
引言
在微服务架构体系中,用户服务是所有业务服务的基础与核心,承担着用户身份认证、权限管控的关键职责。而注册登录、无状态认证、精细化权限控制,更是企业级微服务的必备能力 ------ 很多开发者在落地时,往往会遇到 JWT 配置不规范、权限粒度粗糙、认证流程有漏洞、密码存储不安全等问题,最终导致用户服务无法满足生产环境的要求。
本文将手把手带你开发一个企业级 Spring Cloud 用户服务,核心覆盖三大模块:用户注册与登录(含数据校验、密码加密)、JWT 无状态认证体系(含令牌生成、验证、刷新)、基于 Spring Security 的 RBAC 权限控制(含接口级权限拦截)。本文注重实战落地,所有代码示例均可直接复现,同时详解底层原理与避坑要点,兼顾深度与实用性,助力你快速搭建稳定、安全的微服务用户中心。
1. 前置认知:微服务中用户服务的核心价值与常见痛点
1.1 核心价值
用户服务作为微服务架构的认证中心与权限数据源,其核心价值体现在三个方面:
- 身份认证:验证用户身份合法性,为合法用户颁发访问凭证,拒绝非法访问;
- 权限管控:基于用户角色与权限,控制用户对各微服务接口的访问范围,实现精细化授权;
- 数据支撑:为其他业务服务提供用户基础数据(如用户信息、角色信息),支撑业务逻辑落地。
在微服务架构中,用户服务的核心地位可通过下图直观展示:

1.2 常见痛点
开发者在开发用户服务时,容易陷入以下几个痛点,导致服务无法满足企业级要求:
- 密码存储不安全:直接明文存储用户密码,或使用简单加密方式,存在泄露风险;
- JWT 配置不规范:密钥硬编码、令牌无过期策略、无刷新机制,导致认证体系脆弱;
- 权限控制粗糙:仅实现全局登录拦截,无法实现接口级、角色级的精细化权限控制;
- 无统一异常处理:认证、权限异常返回格式混乱,不利于前端对接;
- 状态化认证:使用 Session 保存用户状态,无法满足微服务集群的无状态要求。
2. 技术选型:构建企业级用户服务的技术栈清单
本文采用当前主流、生态完善的技术栈,确保用户服务的稳定性、可扩展性和安全性,具体选型如下:
| 技术领域 | 技术选型 | 选型理由 |
|---|---|---|
| 核心框架 | Spring Boot 3.2 + Spring Cloud Alibaba 2023.0.1.0 | 主流微服务框架,生态完善,文档丰富,企业落地案例多 |
| 认证方案 | JWT(JSON Web Token) | 无状态认证,支持跨服务、跨域,适合微服务集群部署 |
| 安全框架 | Spring Security | 与 Spring 生态无缝整合,提供完整的认证、授权、拦截能力 |
| 数据持久层 | MyBatis-Plus 3.5.5 | 简化 MyBatis 开发,提供 CRUD、分页、条件查询等便捷功能 |
| 数据库 | MySQL 8.0 | 稳定、高效、开源,企业级应用的首选关系型数据库 |
| 参数校验 | Hibernate Validator 8.0.1 | 支持注解式参数校验,简化请求参数合法性判断 |
| 工具类 | Hutool 5.8.20 | 提供 JWT 操作、加密、日期处理等工具,简化重复开发 |
| 密码加密 | BCrypt | Spring Security 内置加密算法,支持加盐哈希,安全性高 |
3. 环境搭建:Spring Cloud 项目初始化与基础配置
3.1 项目初始化
创建 Spring Boot 项目(命名为 user-service),引入核心依赖,pom.xml 关键配置如下:
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-service</name>
<dependencies>
<!-- Spring Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MyBatis-Plus 数据持久层 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Hibernate Validator 参数校验 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<!-- Hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
<!-- Lombok 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3.2 基础配置
编写 application.yml 配置文件,配置数据库、MyBatis-Plus、JWT 基础参数(生产环境建议通过 Spring Cloud Config 集中配置):
bash
server:
port: 8080 # 服务端口
spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user_service?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root123456
# JWT 自定义配置(可根据业务调整)
jwt:
secret: spring-cloud-user-service-jwt-secret-2024 # JWT 签名密钥,生产环境需加密存储
access-token-expire: 7200000 # 访问令牌过期时间:2小时(毫秒)
refresh-token-expire: 604800000 # 刷新令牌过期时间:7天(毫秒)
# MyBatis-Plus 配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml # Mapper XML 文件路径
type-aliases-package: com.example.userservice.entity # 实体类别名包
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(开发环境)
3.3 项目结构搭建
搭建清晰的项目目录结构,便于后续维护和扩展:
bash
user-service/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── userservice/
│ │ │ ├── UserServiceApplication.java # 启动类
│ │ │ ├── entity/ # 实体类(User、Role等)
│ │ │ ├── mapper/ # Mapper 接口
│ │ │ ├── service/ # 业务层接口
│ │ │ │ └── impl/ # 业务层实现类
│ │ │ ├── controller/ # 控制层接口
│ │ │ ├── config/ # 配置类(Security、JWT等)
│ │ │ ├── util/ # 工具类(JWT工具、结果封装等)
│ │ │ └── exception/ # 异常处理(全局异常、认证异常等)
│ │ └── resources/
│ │ ├── mapper/ # Mapper XML 文件
│ │ ├── application.yml # 配置文件
│ │ └── db/ # 数据库脚本
│ └── test/ # 测试类
└── pom.xml # 依赖配置
4. 核心模块一:用户注册与登录(数据层 → 业务层 → 接口层)
用户注册与登录是用户服务的基础功能,核心要求是数据校验严格、密码存储安全、接口返回统一。
4.1 数据模型设计
采用 RBAC 模型的核心实体,设计 user(用户表)和 role(角色表),并建立用户 - 角色多对多关联表 user_role(本文先实现核心功能,关联表后续扩展)。
4.1.1 数据库脚本
创建 user_service 数据库,执行以下 SQL 脚本:
sql
-- 用户表
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名(唯一)',
`password` varchar(100) NOT NULL COMMENT '加密后的密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '用户昵称',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`status` tinyint(1) DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) NOT NULL COMMENT '角色名称(如ADMIN、USER)',
`role_desc` varchar(200) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
4.1.2 实体类编写
编写 User 实体类(使用 Lombok 简化代码):
java
package com.example.userservice.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class User {
/**
* 用户ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名(唯一)
*/
private String username;
/**
* 加密后的密码
*/
private String password;
/**
* 用户昵称
*/
private String nickname;
/**
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 状态:1-正常,0-禁用
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
4.2 数据层开发
基于 MyBatis-Plus 编写 UserMapper 接口,简化 CRUD 操作:
java
package com.example.userservice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.userservice.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 用户信息
*/
User selectUserByUsername(String username);
}
编写 UserMapper.xml,实现根据用户名查询用户的 SQL:
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.userservice.mapper.UserMapper">
<select id="selectUserByUsername" resultType="com.example.userservice.entity.User">
SELECT id, username, password, nickname, phone, email, status, create_time, update_time
FROM user
WHERE username = #{username}
</select>
</mapper>
4.3 业务层开发
4.3.1 结果封装工具
创建统一结果封装类 Result,便于接口返回格式统一:
java
package com.example.userservice.util;
import lombok.Data;
/**
* 统一返回结果
*/
@Data
public class Result<T> {
/**
* 响应码:200-成功,500-失败
*/
private Integer code;
/**
* 响应消息
*/
private String msg;
/**
* 响应数据
*/
private T data;
// 成功响应(无数据)
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
// 成功响应(有数据)
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
// 失败响应
public static <T> Result<T> fail(String msg) {
return new Result<>(500, msg, null);
}
private Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
4.3.2 业务层接口与实现
编写 UserService 接口:
java
package com.example.userservice.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.userservice.entity.User;
import com.example.userservice.util.Result;
/**
* 用户业务层接口
*/
public interface UserService extends IService<User> {
/**
* 用户注册
* @param user 用户注册信息
* @return 注册结果
*/
Result<?> register(User user);
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录结果(含令牌)
*/
Result<?> login(String username, String password);
}
编写 UserServiceImpl 实现类,核心处理注册数据校验、密码加密、登录验证:
java
package com.example.userservice.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.userservice.entity.User;
import com.example.userservice.mapper.UserMapper;
import com.example.userservice.service.UserService;
import com.example.userservice.util.Result;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
/**
* 用户业务层实现类
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private UserMapper userMapper;
/**
* 密码加密器(Spring Security 内置 BCrypt)
*/
@Resource
private BCryptPasswordEncoder passwordEncoder;
@Override
public Result<?> register(User user) {
// 1. 数据校验
if (!StringUtils.hasText(user.getUsername()) || !StringUtils.hasText(user.getPassword())) {
return Result.fail("用户名和密码不能为空");
}
// 2. 校验用户名是否已存在
User existUser = userMapper.selectUserByUsername(user.getUsername());
if (existUser != null) {
return Result.fail("用户名已存在");
}
// 3. 密码加密(BCrypt 加盐哈希,不可逆)
String encodePassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
// 4. 设置默认值
if (user.getStatus() == null) {
user.setStatus(1); // 默认正常状态
}
if (user.getNickname() == null) {
user.setNickname(user.getUsername()); // 默认昵称等于用户名
}
// 5. 数据入库
boolean saveResult = this.save(user);
if (!saveResult) {
return Result.fail("注册失败,请重试");
}
return Result.success("注册成功");
}
@Override
public Result<?> login(String username, String password) {
// 1. 数据校验
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
return Result.fail("用户名和密码不能为空");
}
// 2. 查询用户信息
User user = userMapper.selectUserByUsername(username);
if (user == null) {
return Result.fail("用户名不存在");
}
// 3. 校验密码(BCrypt 匹配加密后的密码)
boolean passwordMatch = passwordEncoder.matches(password, user.getPassword());
if (!passwordMatch) {
return Result.fail("密码错误");
}
// 4. 校验用户状态
if (user.getStatus() == 0) {
return Result.fail("用户已被禁用,请联系管理员");
}
// 5. 后续将生成 JWT 令牌,此处先返回登录成功
return Result.success("登录成功");
}
}
4.4 接口层开发
编写 UserController,提供注册和登录接口,并使用 Hibernate Validator 进行参数校验:
java
package com.example.userservice.controller;
import com.example.userservice.entity.User;
import com.example.userservice.service.UserService;
import com.example.userservice.util.Result;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 用户控制层接口
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
@Resource
private UserService userService;
/**
* 用户注册接口
*/
@PostMapping("/register")
public Result<?> register(@Valid User user) {
return userService.register(user);
}
/**
* 用户登录接口
*/
@PostMapping("/login")
public Result<?> login(@RequestParam String username, @RequestParam String password) {
return userService.login(username, password);
}
}
5. 核心模块二:JWT 认证体系搭建(生成 → 验证 → 刷新)
JWT 是实现微服务无状态认证的核心,本文基于 Hutool 工具类搭建完整的 JWT 认证体系,包括令牌生成、验证、刷新三大功能。
5.1 JWT 核心原理
JWT 由三部分组成,以 . 分隔:
- Header(头部):指定令牌类型和签名算法(如 HS256);
- Payload(载荷):存储用户核心信息(如用户 ID、用户名、角色),不可存储敏感信息(如密码);
- Signature(签名):使用头部指定的算法,结合 JWT 密钥对头部和载荷进行加密,防止令牌被篡改。
5.2 JWT 工具类实现
创建 JwtUtil 工具类,封装 JWT 令牌的生成、验证、解析功能:
java
package com.example.userservice.util;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTCreator;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT 工具类
*/
@Component
public class JwtUtil {
/**
* JWT 签名密钥
*/
@Value("${spring.jwt.secret}")
private String jwtSecret;
/**
* 访问令牌过期时间
*/
@Value("${spring.jwt.access-token-expire}")
private long accessTokenExpire;
/**
* 刷新令牌过期时间
*/
@Value("${spring.jwt.refresh-token-expire}")
private long refreshTokenExpire;
/**
* 创建 JWTSigner
*/
private JWTSigner getJWTSigner() {
return JWTSignerUtil.hs256(jwtSecret.getBytes());
}
/**
* 生成访问令牌
* @param userId 用户ID
* @param username 用户名
* @return 访问令牌
*/
public String generateAccessToken(Long userId, String username) {
return generateToken(userId, username, accessTokenExpire);
}
/**
* 生成刷新令牌
* @param userId 用户ID
* @param username 用户名
* @return 刷新令牌
*/
public String generateRefreshToken(Long userId, String username) {
return generateToken(userId, username, refreshTokenExpire);
}
/**
* 生成 JWT 令牌
* @param userId 用户ID
* @param username 用户名
* @param expire 过期时间(毫秒)
* @return JWT 令牌
*/
private String generateToken(Long userId, String username, long expire) {
// 当前时间
Date now = new Date();
// 过期时间
Date expireDate = new Date(now.getTime() + expire);
// 构建 JWT 载荷
JWTCreator jwtCreator = JWT.create()
.setHeader("typ", "JWT") // 令牌类型
.setHeader("alg", "HS256") // 签名算法
.setPayload("userId", userId) // 用户ID
.setPayload("username", username) // 用户名
.setIssuedAt(now) // 签发时间
.setExpiresAt(expireDate); // 过期时间
// 签名并返回令牌
return jwtCreator.sign(getJWTSigner());
}
/**
* 验证 JWT 令牌有效性
* @param token JWT 令牌
* @return 验证结果:true-有效,false-无效
*/
public boolean validateToken(String token) {
try {
JWT jwt = JWT.parse(token);
// 验证签名和过期时间
JWTValidator.of(jwt).validateSignature(getJWTSigner()).validateDate();
return true;
} catch (Exception e) {
// 令牌无效、过期、签名错误等均返回 false
return false;
}
}
/**
* 解析 JWT 令牌,获取用户信息
* @param token JWT 令牌
* @return 用户信息(userId、username)
*/
public Map<String, Object> parseToken(String token) {
JWT jwt = JWT.parse(token);
Map<String, Object> userInfo = new HashMap<>(2);
userInfo.put("userId", jwt.getPayload("userId", Long.class));
userInfo.put("username", jwt.getPayload("username", String.class));
return userInfo;
}
}
5.3 完善登录接口(返回 JWT 令牌)
修改 UserServiceImpl 的 login 方法,登录成功后生成访问令牌和刷新令牌并返回:
java
// 注入 JwtUtil
@Resource
private JwtUtil jwtUtil;
@Override
public Result<?> login(String username, String password) {
// 1. 数据校验(省略,同之前)
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
return Result.fail("用户名和密码不能为空");
}
// 2. 查询用户信息(省略,同之前)
User user = userMapper.selectUserByUsername(username);
if (user == null) {
return Result.fail("用户名不存在");
}
// 3. 校验密码(省略,同之前)
boolean passwordMatch = passwordEncoder.matches(password, user.getPassword());
if (!passwordMatch) {
return Result.fail("密码错误");
}
// 4. 校验用户状态(省略,同之前)
if (user.getStatus() == 0) {
return Result.fail("用户已被禁用,请联系管理员");
}
// 5. 生成 JWT 访问令牌和刷新令牌
String accessToken = jwtUtil.generateAccessToken(user.getId(), user.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
// 6. 封装返回结果
Map<String, String> tokenMap = new HashMap<>(2);
tokenMap.put("accessToken", accessToken);
tokenMap.put("refreshToken", refreshToken);
return Result.success(tokenMap);
}
5.4 JWT 认证过滤器实现
创建 JwtAuthenticationFilter,继承 OncePerRequestFilter,实现对请求的拦截和 JWT 令牌的验证:
java
package com.example.userservice.config;
import com.example.userservice.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
/**
* JWT 认证过滤器
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 1. 从请求头中获取 JWT 令牌
String token = getTokenFromRequest(request);
if (!StringUtils.hasText(token)) {
// 无令牌,直接放行(后续 Security 会拦截未认证请求)
filterChain.doFilter(request, response);
return;
}
// 2. 验证 JWT 令牌有效性
if (!jwtUtil.validateToken(token)) {
// 令牌无效,直接放行(后续 Security 会拦截未认证请求)
filterChain.doFilter(request, response);
return;
}
// 3. 解析令牌,获取用户信息
Map<String, Object> userInfo = jwtUtil.parseToken(token);
String username = (String) userInfo.get("username");
// 4. 将用户信息存入 SecurityContext(表示用户已认证)
if (StringUtils.hasText(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
// 构建 UserDetails(此处简化,后续结合角色权限扩展)
UserDetails userDetails = User.withUsername(username)
.password("") // 密码已加密,此处无需存储
.authorities(Collections.emptyList())
.build();
// 构建认证令牌
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 存入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
// 5. 放行请求
filterChain.doFilter(request, response);
}
/**
* 从请求头中获取 JWT 令牌
* @param request 请求对象
* @return JWT 令牌
*/
private String getTokenFromRequest(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7); // 去除 "Bearer " 前缀
}
return null;
}
}
5.5 令牌刷新接口实现
在 UserController 中添加令牌刷新接口,通过有效的刷新令牌生成新的访问令牌:
java
/**
* 令牌刷新接口
*/
@PostMapping("/refresh")
public Result<?> refreshToken(@RequestParam String refreshToken) {
// 1. 校验刷新令牌是否有效
if (!jwtUtil.validateToken(refreshToken)) {
return Result.fail("刷新令牌无效或已过期,请重新登录");
}
// 2. 解析刷新令牌,获取用户信息
Map<String, Object> userInfo = jwtUtil.parseToken(refreshToken);
Long userId = (Long) userInfo.get("userId");
String username = (String) userInfo.get("username");
// 3. 生成新的访问令牌
String newAccessToken = jwtUtil.generateAccessToken(userId, username);
// 4. 封装返回结果
Map<String, String> tokenMap = new HashMap<>(1);
tokenMap.put("accessToken", newAccessToken);
return Result.success(tokenMap);
}
6. 核心模块三:基于 Spring Security 的 RBAC 权限控制
基于 Spring Security 整合 RBAC 模型,实现接口级、角色级的精细化权限控制,核心包括 Security 配置、自定义 UserDetailsService、权限注解使用三大步骤。
6.1 RBAC 权限模型原理
RBAC(基于角色的访问控制)模型的核心是用户 - 角色 - 权限的三层关联:
- 用户:系统的操作者,一个用户可拥有多个角色;
- 角色:权限的集合,一个角色可拥有多个权限;
- 权限:访问接口或资源的许可,一个权限可分配给多个角色。
RBAC 权限控制流程如下:

6.2 Spring Security 核心配置
创建 SecurityConfig,配置放行接口、添加 JWT 过滤器、关闭默认表单登录、开启方法级权限控制:
java
package com.example.userservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
/**
* Spring Security 核心配置类
*/
@Configuration
@EnableWebSecurity // 启用 Web 安全
@EnableMethodSecurity // 启用方法级权限控制(支持 @PreAuthorize 等注解)
public class SecurityConfig {
@Resource
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 密码加密器(BCrypt)
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 认证管理器
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 安全过滤链配置
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 关闭 CSRF 防护(前后端分离、JWT 认证无需 CSRF)
.csrf(csrf -> csrf.disable())
// 关闭默认表单登录
.formLogin(form -> form.disable())
// 关闭默认 HTTP 基本认证
.httpBasic(basic -> basic.disable())
// 配置会话管理:无状态(不使用 Session)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 配置请求授权
.authorizeHttpRequests(auth -> auth
// 放行注册、登录、刷新令牌接口(无需认证)
.requestMatchers("/api/user/register", "/api/user/login", "/api/user/refresh").permitAll()
// 其他所有请求均需认证
.anyRequest().authenticated()
)
// 添加 JWT 认证过滤器(在 UsernamePasswordAuthenticationFilter 之前)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
6.3 自定义 UserDetailsService
创建 CustomUserDetailsService,实现 UserDetailsService 接口,查询用户信息及关联角色,为 Spring Security 提供认证和授权数据:
java
package com.example.userservice.config;
import com.example.userservice.entity.User;
import com.example.userservice.mapper.UserMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
/**
* 自定义 UserDetailsService(提供用户认证和授权数据)
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户信息
User user = userMapper.selectUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 2. 校验用户状态(后续可扩展角色、权限查询)
if (user.getStatus() == 0) {
throw new UsernameNotFoundException("用户已被禁用");
}
// 3. 构建 UserDetails(此处简化,后续可添加角色和权限)
// 实际项目中,需从 user_role、role_permission 表中查询用户的角色和权限
return org.springframework.security.core.userdetails.User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(Collections.singletonList("ROLE_USER")) // 默认赋予 USER 角色
.build();
}
}
6.4 接口级权限控制实现
在 UserController 中添加需要权限控制的接口,使用 @PreAuthorize 注解实现角色级权限控制:
java
/**
* 获取当前用户信息(仅 ROLE_USER 角色可访问)
*/
@GetMapping("/info")
@PreAuthorize("hasRole('USER')")
public Result<?> getUserInfo() {
// 从 SecurityContext 中获取当前登录用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
// 查询用户详细信息
User user = userMapper.selectUserByUsername(username);
if (user == null) {
return Result.fail("用户信息不存在");
}
// 脱敏处理(不返回密码等敏感信息)
user.setPassword(null);
return Result.success(user);
}
/**
* 测试管理员权限接口(仅 ROLE_ADMIN 角色可访问)
*/
@GetMapping("/admin/test")
@PreAuthorize("hasRole('ADMIN')")
public Result<?> adminTest() {
return Result.success("管理员接口访问成功");
}
6.5 统一异常处理
创建 GlobalExceptionHandler,处理认证和权限异常,返回统一格式结果:
java
package com.example.userservice.exception;
import com.example.userservice.util.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理认证异常(如未登录、令牌无效)
*/
@ExceptionHandler(AuthenticationException.class)
public Result<?> handleAuthenticationException(AuthenticationException e) {
return Result.fail("认证失败:" + e.getMessage());
}
/**
* 处理权限异常(如无权限访问接口)
*/
@ExceptionHandler(AccessDeniedException.class)
public Result<?> handleAccessDeniedException(AccessDeniedException e) {
return Result.fail("权限不足,无法访问该接口");
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
return Result.fail("系统异常:" + e.getMessage());
}
}
7. 实战测试:接口联调与权限验证
使用 Postman 或 ApiPost 进行接口联调,验证注册、登录、JWT 认证、权限控制是否生效,完整测试流程如下:
7.1 测试步骤 1:用户注册
-
请求地址 :
POST http://localhost:8080/api/user/register -
请求参数 (JSON):
bash{ "username": "testuser", "password": "123456", "nickname": "测试用户", "phone": "13800138000", "email": "testuser@example.com" } -
预期结果 :返回
{"code":200,"msg":"操作成功","data":"注册成功"}
7.2 测试步骤 2:用户登录
- 请求地址 :
POST http://localhost:8080/api/user/login - 请求参数 (表单):
username=testuser&password=123456 - 预期结果 :返回包含
accessToken和refreshToken的结果,令牌有效。
7.3 测试步骤 3:获取用户信息(需认证)
- 请求地址 :
GET http://localhost:8080/api/user/info - 请求头 :
Authorization: Bearer {accessToken}(替换为登录获取的访问令牌) - 预期结果:返回当前用户信息(密码脱敏),状态码 200。
7.4 测试步骤 4:测试管理员权限接口(无权限)
- 请求地址 :
GET http://localhost:8080/api/user/admin/test - 请求头 :
Authorization: Bearer {accessToken} - 预期结果 :返回
{"code":500,"msg":"权限不足,无法访问该接口","data":null}。
7.5 测试步骤 5:令牌刷新
- 请求地址 :
POST http://localhost:8080/api/user/refresh - 请求参数 (表单):
refreshToken={refreshToken}(替换为登录获取的刷新令牌) - 预期结果 :返回新的
accessToken,可用于继续访问需要认证的接口。
8. 避坑指南:企业级落地的 5 个核心注意点
8.1 避坑 1:JWT 密钥硬编码与安全存储
问题 :将 JWT 密钥直接写在 application.yml 中,生产环境存在泄露风险。解决方案:生产环境通过 Spring Cloud Config 或密钥管理服务(如阿里云 KMS)集中存储并加密密钥,通过环境变量注入应用。
8.2 避坑 2:密码加密与校验不规范
问题 :自定义简单加密算法,或直接存储明文密码,安全性低。解决方案 :必须使用 Spring Security 内置的 BCryptPasswordEncoder,其支持自动加盐,哈希结果不可逆,安全性远高于自定义加密。
8.3 避坑 3:权限注解失效
问题 :使用 @PreAuthorize 注解后,无权限请求未被拦截,注解失效。解决方案 :忘记在 SecurityConfig 上添加 @EnableMethodSecurity 注解,开启方法级权限控制即可。
8.4 避坑 4:JWT 令牌存储敏感信息
问题 :在 JWT 载荷中存储密码、手机号等敏感信息,存在泄露风险。解决方案:JWT 载荷仅存储非敏感的核心用户信息(如用户 ID、用户名),敏感信息通过接口查询并脱敏返回。
8.5 避坑 5:无令牌过期重试机制
问题 :访问令牌过期后,直接返回异常,用户体验差。解决方案:前端实现令牌过期拦截,当收到 401 异常时,自动调用刷新令牌接口获取新令牌,重新发起请求,无需用户手动登录。
9. 总结与展望
9.1 核心总结
本文完整实现了一个企业级 Spring Cloud 用户服务,核心成果包括:
- 搭建了完整的用户注册与登录体系,实现了数据校验、密码加密、统一结果返回;
- 基于 JWT 构建了无状态认证体系,实现了令牌生成、验证、刷新三大核心功能;
- 基于 Spring Security 整合 RBAC 模型,实现了接口级、角色级的精细化权限控制;
- 提供了完整的实战测试流程和企业级落地避坑指南,确保服务可直接落地生产环境。
9.2 未来展望
后续可对该用户服务进行扩展,满足更复杂的企业级需求:
- 第三方登录集成:支持微信、支付宝、GitHub 等第三方登录,丰富登录方式;
- 短信 / 邮箱验证码:注册、登录、找回密码时添加验证码验证,提高安全性;
- 完整 RBAC 权限体系:扩展用户 - 角色 - 权限关联表,实现更精细化的权限管控;
- 微服务集成:将用户服务注册到 Nacos 注册中心,为其他微服务提供认证和权限数据;
- 用户信息脱敏与审计:实现用户信息脱敏返回,添加用户操作审计日志,满足合规要求。
点赞 + 收藏 + 关注,获取更多 Spring Cloud 微服务实战干货!有任何用户服务开发的问题,欢迎在评论区留言讨论~
写在最后
本文所有代码示例均可直接复现,已通过实际测试验证有效性。该用户服务作为微服务架构的核心基础,可直接扩展并集成到企业级微服务项目中,助力你快速搭建稳定、安全的微服务体系。
