| name | microservice-auth |
| description | IOE-DREAM微服务认证架构规范。涵盖Gateway集中认证(JWT验证、Token黑名单)、业务层授权(@PermissionCheck注解)、Spring Security依赖管理(业务服务排除)、Gateway身份头转发(X-User-Id等HTTP头)、RBAC规则引擎。使用时机:实现权限验证、配置Gateway、调试认证问题时。 |
microservice-auth - 微服务认证技能
版本: v1.0.0 更新时间: 2025-01-05 适用范围: IOE-DREAM微服务认证与授权
🎯 触发条件
当用户进行以下操作时,应调用本技能:
- 实现认证授权功能时
- 使用@PermissionCheck注解时
- 配置Spring Security依赖时
- Gateway路由配置时
- JWT Token验证时
- 用户身份信息获取时
- 权限规则设计时
- 提到"认证"、"授权"、"权限"、"Gateway"、"JWT"等关键词时
📚 核心认证架构
1. Gateway集中认证架构
架构原则
认证在Gateway层面统一处理,授权在业务层灵活控制
┌─────────────────────────────────────────────────────────┐
│ Gateway网关层(8088) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 1. JWT Token验证 │ │
│ │ - 签名验证 │ │
│ │ - 过期检查 │ │
│ │ - 黑名单检查 │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 2. 用户身份识别 │ │
│ │ - 解析JWT获取userId、username、roles │ │
│ │ - 查询用户详细信息 │ │
│ │ - 查询用户权限列表 │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 3. 身份头转发 │ │
│ │ - X-User-Id: 123456 │ │
│ │ - X-User-Name: admin │ │
│ │ - X-User-Roles: admin,manager,user │ │
│ │ - X-User-Permissions: access:device:query │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 业务微服务层 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 业务层授权(@PermissionCheck注解) │ │
│ │ - 路径匹配: /api/access/device/** │ │
│ │ - 权限验证: 检查X-User-Permissions头 │ │
│ │ - 角色验证: 检查X-User-Roles头 │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 用户身份信息获取 │ │
│ │ - 从HTTP头获取X-User-Id │ │
│ │ - 从HTTP头获取X-User-Name │ │
│ │ - 从HTTP头获取X-User-Roles │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
核心设计
1. Gateway集中认证职责:
- ✅ JWT Token验证(签名、过期、黑名单)
- ✅ 用户身份识别(解析JWT、查询用户信息)
- ✅ 权限列表查询(查询用户权限列表)
- ✅ 身份头转发(X-User-Id、X-User-Name等)
2. 业务层授权职责:
- ✅ 权限验证(@PermissionCheck注解)
- ✅ 角色验证(@PermissionCheck注解)
- ✅ 路径匹配(路径与权限映射)
- ✅ 用户身份信息获取(从HTTP头获取)
3. 无状态设计:
- ✅ 业务服务无Session
- ✅ 完全依赖Gateway转发的身份信息
- ✅ 不依赖Spring Security的Session机制
2. Gateway身份头转发
转发的HTTP头
X-User-Id: 123456 # 用户ID
X-User-Name: admin # 用户名
X-User-Roles: admin,security_manager,user # 角色列表(逗号分隔)
X-User-Permissions: access:device:query,access:area:view # 权限列表(逗号分隔)
Gateway转发配置
// ioedream-gateway-service/src/main/java/net/lab1024/sa/gateway/filter/AuthenticationFilter.java
@Component
@Slf4j
public class AuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserServiceClient userServiceClient;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = extractToken(exchange.getRequest());
if (StringUtils.isBlank(token)) {
return unauthorized(exchange, "Missing token");
}
try {
// 1. 验证JWT Token
if (!jwtTokenProvider.validateToken(token)) {
return unauthorized(exchange, "Invalid token");
}
// 2. 解析用户ID
Long userId = jwtTokenProvider.getUserIdFromToken(token);
// 3. 查询用户信息
UserInfoResponse userInfo = userServiceClient.getUserInfo(userId);
// 4. 添加身份头到请求
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-User-Id", String.valueOf(userId))
.header("X-User-Name", userInfo.getUsername())
.header("X-User-Roles", String.join(",", userInfo.getRoles()))
.header("X-User-Permissions", String.join(",", userInfo.getPermissions()))
.build();
// 5. 继续过滤器链
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (Exception e) {
log.error("[Gateway] Token验证失败: {}", e.getMessage());
return unauthorized(exchange, "Authentication failed");
}
}
private String extractToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":401,\"message\":\"" + message + "\"}";
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
return exchange.getResponse().writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100; // 确保在其他过滤器之前执行
}
}
3. @PermissionCheck权限注解
注解定义
// microservices-common-permission/src/main/java/net/lab1024/sa/common/permission/PermissionCheck.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionCheck {
/**
* 权限标识(多个用逗号分隔)
*
* 示例:
* - 单个权限: "access:device:query"
* - 多个权限(满足其一即可): "access:device:query,access:device:add"
*/
String permission() default "";
/**
* 角色标识(多个用逗号分隔)
*
* 示例:
* - 单个角色: "admin"
* - 多个角色(满足其一即可): "admin,security_manager"
*/
String role() default "";
/**
* 资源路径(可选,用于路径匹配)
*
* 示例:"/api/access/device/**"
*/
String resource() default "";
}
注解处理器
// microservices-common-permission/src/main/java/net/lab1024/sa/common/permission/PermissionCheckAspect.java
@Aspect
@Component
@Slf4j
public class PermissionCheckAspect {
@Override
public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取请求对象
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 2. 从HTTP头获取用户身份信息
String userId = request.getHeader("X-User-Id");
String roles = request.getHeader("X-User-Roles");
String permissions = request.getHeader("X-User-Permissions");
if (StringUtils.isBlank(userId)) {
throw new BusinessException("未登录或登录已过期");
}
// 3. 获取@PermissionCheck注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
PermissionCheck permissionCheck =
signature.getMethod().getAnnotation(PermissionCheck.class);
if (permissionCheck == null) {
// 没有权限注解,直接放行
return joinPoint.proceed();
}
// 4. 权限验证
if (!hasPermission(permissionCheck, roles, permissions)) {
log.warn("[权限验证] 用户{}无权限访问{}",
userId, signature.getMethod().getName());
throw new BusinessException("权限不足");
}
// 5. 权限验证通过,继续执行
return joinPoint.proceed();
}
/**
* 权限验证
*/
private boolean hasPermission(PermissionCheck permissionCheck,
String roles, String permissions) {
// 1. 角色验证
String requiredRole = permissionCheck.role();
if (StringUtils.isNotBlank(requiredRole)) {
List<String> roleList = Arrays.asList(requiredRole.split(","));
List<String> userRoles = Arrays.asList(roles.split(","));
boolean hasRole = roleList.stream().anyMatch(userRoles::contains);
if (!hasRole) {
return false;
}
}
// 2. 权限验证
String requiredPermission = permissionCheck.permission();
if (StringUtils.isNotBlank(requiredPermission)) {
List<String> permissionList = Arrays.asList(requiredPermission.split(","));
List<String> userPermissions = Arrays.asList(permissions.split(","));
boolean hasPermission = permissionList.stream()
.anyMatch(userPermissions::contains);
if (!hasPermission) {
return false;
}
}
return true;
}
}
4. Controller使用@PermissionCheck
标准用法
// microservices/ioedream-access-service/src/main/java/net/lab1024/sa/access/controller/AccessDeviceController.java
@RestController
@RequestMapping("/api/access/device")
@Slf4j
public class AccessDeviceController {
@Autowired
private AccessDeviceService deviceService;
/**
* 查询设备列表
*
* 权限:access:device:query
*/
@GetMapping("/query")
@PermissionCheck(permission = "access:device:query")
public ResponseDTO<PageResult<AccessDeviceVO>> queryDevice(
@Valid AccessDeviceQueryForm form) {
PageResult<AccessDeviceVO> pageResult = deviceService.queryDevice(form);
return ResponseDTO.ok(pageResult);
}
/**
* 添加设备
*
* 权限:access:device:add
* 角色:admin 或 security_manager
*/
@PostMapping("/add")
@PermissionCheck(permission = "access:device:add", role = "admin,security_manager")
public ResponseDTO<Long> addDevice(@Valid @RequestBody AccessDeviceAddForm form) {
Long deviceId = deviceService.addDevice(form);
return ResponseDTO.ok(deviceId);
}
/**
* 更新设备
*
* 权限:access:device:update
*/
@PostMapping("/update")
@PermissionCheck(permission = "access:device:update")
public ResponseDTO<Boolean> updateDevice(@Valid @RequestBody AccessDeviceUpdateForm form) {
Boolean result = deviceService.updateDevice(form);
return ResponseDTO.ok(result);
}
/**
* 删除设备
*
* 权限:access:device:delete
* 角色:仅admin
*/
@PostMapping("/delete")
@PermissionCheck(permission = "access:device:delete", role = "admin")
public ResponseDTO<Boolean> deleteDevice(@RequestBody Long deviceId) {
Boolean result = deviceService.deleteDevice(deviceId);
return ResponseDTO.ok(result);
}
}
5. 业务层获取用户身份信息
工具类
// microservices-common-permission/src/main/java/net/lab1024/sa/common/permission/UserContextHolder.java
public class UserContextHolder {
/**
* 获取当前用户ID
*/
public static Long getUserId() {
HttpServletRequest request = getHttpServletRequest();
String userId = request.getHeader("X-User-Id");
if (StringUtils.isBlank(userId)) {
throw new BusinessException("未登录或登录已过期");
}
return Long.parseLong(userId);
}
/**
* 获取当前用户名
*/
public static String getUserName() {
HttpServletRequest request = getHttpServletRequest();
String userName = request.getHeader("X-User-Name");
return userName;
}
/**
* 获取当前用户角色列表
*/
public static List<String> getUserRoles() {
HttpServletRequest request = getHttpServletRequest();
String roles = request.getHeader("X-User-Roles");
if (StringUtils.isBlank(roles)) {
return Collections.emptyList();
}
return Arrays.asList(roles.split(","));
}
/**
* 获取当前用户权限列表
*/
public static List<String> getUserPermissions() {
HttpServletRequest request = getHttpServletRequest();
String permissions = request.getHeader("X-User-Permissions");
if (StringUtils.isBlank(permissions)) {
return Collections.emptyList();
}
return Arrays.asList(permissions.split(","));
}
/**
* 检查当前用户是否有指定角色
*/
public static boolean hasRole(String role) {
List<String> roles = getUserRoles();
return roles.contains(role);
}
/**
* 检查当前用户是否有指定权限
*/
public static boolean hasPermission(String permission) {
List<String> permissions = getUserPermissions();
return permissions.contains(permission);
}
private static HttpServletRequest getHttpServletRequest() {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new BusinessException("无法获取请求上下文");
}
return attributes.getRequest();
}
}
Service层使用示例
// microservices/ioedream-access-service/src/main/java/net/lab1024/sa/access/service/impl/AccessDeviceServiceImpl.java
@Service
@Slf4j
public class AccessDeviceServiceImpl implements AccessDeviceService {
@Autowired
private AccessDeviceManager deviceManager;
@Override
public PageResult<AccessDeviceVO> queryDevice(AccessDeviceQueryForm form) {
// 获取当前用户ID
Long userId = UserContextHolder.getUserId();
// 获取当前用户角色
List<String> roles = UserContextHolder.getUserRoles();
log.info("[门禁管理] 用户{}查询设备列表,角色:{}", userId, roles);
// 业务逻辑:权限范围过滤
if (!UserContextHolder.hasRole("admin")) {
// 非管理员只能查看授权区域的设备
form.setAreaIds(getUserAuthorizedAreaIds(userId));
}
return deviceManager.queryDevice(form);
}
private List<Long> getUserAuthorizedAreaIds(Long userId) {
// 查询用户授权的区域ID列表
// ... 省略实现
return new ArrayList<>();
}
}
6. Spring Security依赖管理
Gateway服务(保留Spring Security)
<!-- ioedream-gateway-service/pom.xml -->
<dependencies>
<!-- Gateway服务保留Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<!-- 其他依赖... -->
</dependencies>
业务服务(排除Spring Security)
<!-- ioedream-access-service/pom.xml -->
<dependencies>
<!-- ❌ 业务服务不得引入Spring Security -->
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-->
<!-- ✅ 使用common-permission模块 -->
<dependency>
<groupId>net.lab1024</groupId>
<artifactId>microservices-common-permission</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 其他依赖... -->
</dependencies>
细粒度模块(common-security)
<!-- microservices-common-security/pom.xml -->
<dependencies>
<!-- common-security保留Spring Security(用于Gateway) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<!-- ❌ 不传递给业务服务 -->
<!-- 业务服务通过Gateway转发身份信息,不直接依赖common-security -->
</dependencies>
⚠️ 禁止事项
认证架构层面
❌ 禁止业务服务引入Spring Security:
- ❌ 业务服务pom.xml中不得有
spring-boot-starter-security依赖 - ❌ 业务服务不得使用
@PreAuthorize注解 - ❌ 业务服务不得使用
SecurityContextHolder获取用户信息
- ❌ 业务服务pom.xml中不得有
❌ 禁止绕过Gateway直接访问业务服务:
- ❌ 客户端不得直接调用业务服务(必须通过Gateway)
- ❌ 业务服务不得暴露非/api开头的内部接口
❌ 禁止业务服务实现认证逻辑:
- ❌ 业务服务不得验证JWT Token
- ❌ 业务服务不得查询用户信息(应由Gateway查询)
授权规范层面
❌ 禁止使用错误的权限注解:
- ❌ 不得使用
@PreAuthorize(应使用@PermissionCheck) - ❌ 不得使用
@Secured(应使用@PermissionCheck)
- ❌ 不得使用
❌ 禁止权限标识不规范:
- ❌ 权限标识不清晰(如"query"、"add")
- ❌ 权限标识不含模块前缀(如"device:query"应为"access:device:query")
身份信息获取层面
❌ 禁止从Session获取用户信息:
- ❌ 不得使用
request.getSession().getAttribute("userId") - ❌ 不得使用
SecurityContextHolder.getContext().getAuthentication()
- ❌ 不得使用
❌ 禁止硬编码用户信息:
- ❌ 不得在代码中固定userId、userName
- ❌ 不得绕过权限检查
✅ 认证授权检查清单
Gateway配置检查
- Gateway保留Spring Security依赖
- JWT Token验证功能完整
- 身份头转发功能完整(X-User-Id等)
- 用户信息查询功能完整
- 黑名单检查功能完整
业务服务配置检查
- 业务服务排除Spring Security依赖
- 业务服务引入common-permission模块
- Controller使用@PermissionCheck注解
- Service使用UserContextHolder获取用户信息
- 无@PreAuthorize注解
权限规范检查
- 权限标识规范(模块:功能:操作)
- 角色标识规范(admin、manager、user等)
- 权限验证逻辑完整
- 权限不足时抛出BusinessException
依赖管理检查
- Gateway依赖common-security
- 业务服务依赖common-permission
- 业务服务不依赖common-security
- 无循环依赖
🔍 相关技能
- architecture-core: 架构设计核心(细粒度模块架构、四层架构)
- controller-dev: Controller开发规范(@PermissionCheck注解使用)
- service-dev: Service开发规范(UserContextHolder使用)
- api-design: API设计规范(路径权限映射)
维护者: IOE-DREAM架构委员会 最后更新: 2025-01-05 版本: v1.0.0