# security **Repository Path**: molost/security ## Basic Information - **Project Name**: security - **Description**: 基于 Spring Boot 3 + Spring Security + OAuth2 + JWT 的微服务认证授权系统。 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-02-12 - **Last Updated**: 2026-02-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RBAC 微服务认证授权平台 基于 Spring Boot 3 + Spring Security + OAuth2 + JWT 的微服务认证授权系统。 --- ## 一、项目概述 ### 1.1 系统架构 ``` ┌─────────────────┐ │ MySQL 数据库 │ │ rbac_demo │ └────────┬────────┘ │ ┌────────────────────────┼────────────────────────┐ │ │ │ ┌──────────┐ ┌────┴────────┐ ┌────┴────┐ ┌───────┴───────┐ │ 客户端 │──→│ Gateway │─────────→│ Auth │ │ Admin │ │ (前端/App)│ │ :8080 │ │ │ :9001 │ │ :9002 │ └──────────┘ │ │ │ └─────────┘ └───────↑────────┘ │ JWT校验 │ │ | │ 路由转发 │────┼─────────────────────────────────--─┘ │ 权限透传 │ │ └─────────────┘ │ ┌─────────────┐ └────→│ Business │ │ :9003 │ └─────────────┘ 路由规则: /auth/** → auth-service (9001) /admin/** → admin-service (9002) /api/** → business-service (9003) ``` ### 1.2 服务列表 | 服务 | 端口 | 职责 | |------|------|------| | gateway-service | 8080 | API 网关,JWT 校验,路由转发,权限透传 | | auth-service | 9001 | OAuth2 授权服务器,JWT 令牌签发 | | admin-service | 9002 | 用户/角色/权限 CRUD 管理 | | business-service | 9003 | 受保护的业务接口,方法级鉴权 | | common | - | 公共模块,工具类 | ### 1.3 技术栈 | 分类 | 技术 | 版本 | |------|------|------| | 核心框架 | Spring Boot | 3.2.4 | | 安全框架 | Spring Security | 6.x | | 授权服务器 | Spring Authorization Server | 1.2.3 | | 网关 | Spring Cloud Gateway | WebFlux | | ORM | MyBatis-Plus | 3.5.5 | | 数据库 | MySQL | 8.0 | | JWT | jjwt | 0.11.5 | | JDK | Java | 17 | --- ## 二、核心原理 ### 2.1 认证流程 ``` ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 客户端 │ │ Gateway │ │ Auth │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ 1. POST /auth/oauth2/token │ │ │ (username + password) │ │ │────────────────────────────→│ │ │ │ 2. 转发请求 │ │ │────────────────────────────→│ │ │ │ │ │ │ 3. 验证用户名密码 │ │ │ 查询用户角色权限 │ │ │ 生成 JWT Token │ │ │ │ │ 4. 返回 JWT │ │ │←────────────────────────────│ │ 5. 返回 JWT │ │ │←────────────────────────────│ │ │ │ │ ``` ### 2.2 鉴权流程 ``` ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 客户端 │ │ Gateway │ │ Business │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ 1. GET /api/goods/list │ │ │ Authorization: Bearer JWT │ │ │────────────────────────────→│ │ │ │ │ │ │ 2. JwtAuthFilter │ │ │ - 校验 JWT 签名 │ │ │ - 检查是否过期 │ │ │ - 解析用户信息 │ │ │ │ │ │ 3. 透传用户信息到 Header │ │ │ X-User-Name: admin │ │ │ X-User-Roles: ROLE_ADMIN │ │ │ X-User-Permissions: ... │ │ │────────────────────────────→│ │ │ │ │ │ │ 4. GatewayAuthFilter │ │ │ Header → SecurityContext │ │ │ │ │ │ 5. UrlPermissionFilter │ │ │ URL级动态鉴权 │ │ │ (查缓存匹配权限) │ │ │ │ │ │ 6. @PreAuthorize 校验权限 │ │ │ 方法级注解鉴权 │ │ │ │ │ 7. 返回数据 │ │ │←────────────────────────────│ │ 8. 返回数据 │ │ │←────────────────────────────│ │ ``` **鉴权层级说明:** | 步骤 | 组件 | 职责 | 失败处理 | |------|------|------|----------| | 4 | GatewayAuthFilter | Header → SecurityContext | 无用户信息则匿名访问 | | 5 | UrlPermissionFilter | URL + Method 动态匹配权限 | 403 权限不足 | | 6 | @PreAuthorize | 方法级注解兜底校验 | 403 权限不足 | ### 2.3 用户信息流转模型 **核心原则:跨进程用 Header,进程内用 ThreadLocal** | 环节 | 存储位置 | 原因 | |------|----------|------| | 客户端 → 网关 | Header (JWT) | HTTP 协议标准方式 | | 网关 → 下游服务 | Header (User-Info) | 跨进程无法共享内存 | | 服务内部 (Filter) | SecurityContext | 转存到 ThreadLocal | | 服务内部 (业务层) | SecurityContext | 避免参数污染 | | 服务 A → 服务 B | RPC 载体 | Dubbo/Feign/gRPC 拦截器重新放入 | ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ 完整流转链路 │ └─────────────────────────────────────────────────────────────────────────┘ 客户端 网关 服务A 服务B │ │ │ │ │ Authorization: JWT │ │ │ │───────────────────────→│ │ │ │ │ │ │ │ │ X-User-Name: admin │ │ │ │ X-User-Roles: ... │ │ │ │───────────────────────→│ │ │ │ ① 跨进程: Header │ │ │ │ │ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ GatewayAuthFilter│ │ │ │ │ │ Header→ThreadLocal│ │ │ │ │ └──────────────────┘ │ │ │ │ ② 进程内: ThreadLocal │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ 业务层 │ │ │ │ │ │ UserContext.get()│ │ │ │ │ └──────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ RPC 拦截器 │ │ │ │ │ │ ThreadLocal→载体 │ │ │ │ │ └──────────────────┘ │ │ │ │ ③ 服务间: 自动传递 │ │ │ │ │ │ │ │ Feign: Header │ │ │ │ Dubbo: Attachment │ │ │ │ gRPC: Metadata │ │ │ │───────────────────────→│ ``` **三层流转模型:** | 层级 | 传输方式 | 实现组件 | |------|----------|----------| | ① 跨进程(网关→服务) | HTTP Header | JwtAuthFilter → GatewayAuthFilter | | ② 进程内(Filter→Controller) | ThreadLocal | SecurityContext / UserContextHolder | | ③ 服务间(服务A→服务B) | RPC 载体 | Feign/Dubbo/gRPC 拦截器(自动装配) | --- ## 三、各服务详解 ### 3.1 Gateway 网关服务 **职责:** 统一入口,JWT 校验,路由转发,权限透传 **核心组件:** ``` gateway-service/ ├── filter/ │ └── JwtAuthFilter.java # JWT 校验过滤器 ├── config/ │ └── CorsConfig.java # 跨域配置 ├── util/ │ └── JwtUtil.java # JWT 解析工具 └── resources/ └── application.yml # 路由配置 ``` **JwtAuthFilter 工作流程:** ```java public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 白名单放行(登录接口等) if (isWhitelist(path)) { return chain.filter(exchange); } // 2. 提取并校验 JWT String token = extractToken(request); if (!jwtUtil.validateToken(token)) { return unauthorized(exchange, "令牌无效"); } // 3. 解析用户信息,透传到 Header Claims claims = jwtUtil.parseToken(token); ServerHttpRequest mutatedRequest = request.mutate() .header("X-User-Name", claims.getSubject()) .header("X-User-Roles", claims.get("roles")) .header("X-User-Permissions", claims.get("permissions")) .build(); // 4. 继续转发 return chain.filter(exchange.mutate().request(mutatedRequest).build()); } ``` **路由配置:** ```yaml spring: cloud: gateway: routes: - id: auth-service uri: http://localhost:9001 predicates: - Path=/auth/** filters: - StripPrefix=1 - id: admin-service uri: http://localhost:9002 predicates: - Path=/admin/** filters: - StripPrefix=1 - id: business-service uri: http://localhost:9003 predicates: - Path=/api/** filters: - StripPrefix=1 ``` **为什么用 WebFlux?** 传统 Servlet 是一个请求一个线程,高并发时线程不够用。WebFlux 是响应式的,少量线程处理大量请求,适合网关这种 IO 密集型场景。 ``` 传统 BIO: 线程1 ──────────────────────────────────────→ │等待数据│处理│等待数据│处理│等待数据│ 1000 个连接 = 1000 个线程(大部分时间在等) Netty NIO: EventLoop线程 ──────────────────────────────→ │处理连接1│处理连接2│处理连接3│... 1000 个连接 = 几个线程(不等待,轮询处理) ``` --- ### 3.2 Auth 认证服务 **职责:** OAuth2 授权服务器,用户认证,JWT 令牌签发 **核心组件:** ``` auth-service/ ├── entity/ │ ├── SysUser.java # 用户实体 │ ├── SysRole.java # 角色实体 │ └── SysPermission.java # 权限实体 ├── mapper/ │ └── UserMapper.java # 用户数据访问 ├── service/impl/ │ └── UserDetailsServiceImpl.java # Spring Security 用户加载 ├── util/ │ ├── SecurityConfig.java # 安全配置 │ ├── OAuthConfig.java # OAuth2 配置 │ └── JwtUtil.java # JWT 工具 └── resources/ └── application.yml ``` **UserDetailsService 实现:** Spring Security 通过 `UserDetailsService` 加载用户信息: ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Resource private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) { // 1. 查询用户 SysUser user = userMapper.selectByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } // 2. 查询角色和权限 List roles = userMapper.selectRolesByUserId(user.getId()); List permissions = userMapper.selectPermissionsByUserId(user.getId()); // 3. 构建权限列表 List authorities = new ArrayList<>(); roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role))); permissions.forEach(perm -> authorities.add(new SimpleGrantedAuthority(perm))); // 4. 返回 UserDetails return new User(user.getUsername(), user.getPassword(), authorities); } } ``` **JWT 结构:** ``` Header.Payload.Signature Header: {"alg": "HS256", "typ": "JWT"} Payload: { "sub": "admin", // 用户名 "roles": "ROLE_ADMIN", // 角色 "permissions": "goods:list,goods:add", // 权限 "exp": 1234567890 // 过期时间 } Signature: HMACSHA256(base64(header) + "." + base64(payload), secret) ``` **为什么用三个 jjwt 包?** ```xml io.jsonwebtoken jjwt-api io.jsonwebtoken jjwt-impl runtime io.jsonwebtoken jjwt-jackson runtime ``` 这是模块化设计:编译时只依赖 API,运行时才加载实现,方便替换。 --- ### 3.3 Admin 管理服务 **职责:** 用户/角色/权限的 CRUD 管理 **核心组件:** ``` admin-service/ ├── entity/ │ ├── SysUser.java │ ├── SysRole.java │ └── SysPermission.java ├── mapper/ │ ├── UserMapper.java │ ├── RoleMapper.java │ └── PermissionMapper.java ├── service/ │ ├── UserService.java │ ├── RoleService.java │ └── PermissionService.java ├── controller/ │ ├── UserController.java │ ├── RoleController.java │ └── PermissionController.java └── resources/ └── mybatis/*.xml ``` **API 接口:** ``` 用户管理: GET /user/list # 查询用户列表 GET /user/{id} # 查询单个用户 POST /user # 新增用户 PUT /user # 更新用户 DELETE /user/{id} # 删除用户 GET /user/{id}/roles # 查询用户的角色 POST /user/{id}/roles # 分配用户角色 角色管理: GET /role/list # 查询角色列表 POST /role/{id}/permissions # 分配角色权限 权限管理: GET /permission/list # 查询权限列表 ``` **RBAC 数据模型:** ``` 用户 ←──多对多──→ 角色 ←──多对多──→ 权限 sys_user_role sys_role_permission ``` --- ### 3.4 Business 业务服务 **职责:** 受保护的业务接口,演示方法级鉴权和数据级权限 **核心组件:** ``` business-service/ ├── entity/ │ └── Goods.java # 商品实体 ├── mapper/ │ └── GoodsMapper.java ├── service/ │ └── GoodsService.java ├── controller/ │ └── GoodsController.java # 方法级鉴权 ├── filter/ │ └── GatewayAuthFilter.java # Header → SecurityContext └── config/ └── SecurityConfig.java # 启用方法级鉴权 ``` **GatewayAuthFilter:** 从网关透传的 Header 中读取用户信息,存入 SecurityContext: ```java @Component public class GatewayAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, ...) { try { String username = request.getHeader("X-User-Name"); if (username != null) { // 构建权限列表 List authorities = new ArrayList<>(); String roles = request.getHeader("X-User-Roles"); if (roles != null) { for (String role : roles.split(",")) { authorities.add(new SimpleGrantedAuthority(role)); } } String permissions = request.getHeader("X-User-Permissions"); if (permissions != null) { for (String perm : permissions.split(",")) { authorities.add(new SimpleGrantedAuthority(perm)); } } // 存入 SecurityContext UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } finally { // 清理,防止线程复用导致数据污染 SecurityContextHolder.clearContext(); } } } ``` **方法级鉴权:** ```java @RestController @RequestMapping("/goods") public class GoodsController { @GetMapping("/list") @PreAuthorize("hasAuthority('goods:list')") // 需要 goods:list 权限 public Result> listGoods() { return Result.success(goodsService.listByDataScope()); } @PostMapping @PreAuthorize("hasAuthority('goods:add')") // 需要 goods:add 权限 public Result saveGoods(@RequestBody Goods goods) { goods.setCreatedBy(UserContext.getUsername()); goodsService.save(goods); return Result.success(); } } ``` **数据级权限:** ```java @Service public class GoodsServiceImpl implements GoodsService { @Override public List listByDataScope() { // 超级管理员查全部 if (UserContext.hasAllDataScope()) { return list(); } // 普通用户只查自己创建的 return lambdaQuery() .eq(Goods::getCreatedBy, UserContext.getUsername()) .list(); } } ``` --- ### 3.5 Common 公共模块 **职责:** 提供跨服务复用的工具类,支持多种 RPC 框架的用户上下文传递 **核心组件:** ``` common/ ├── context/ │ ├── UserContextHolder.java # 用户上下文持有者(统一管理) │ └── propagation/ │ └── ContextPropagator.java # 上下文传播器接口 ├── filter/ │ └── GatewayAuthFilter.java # 网关认证过滤器(通用版) ├── rpc/ │ ├── feign/ # Feign 支持 │ │ ├── FeignUserContextInterceptor.java │ │ └── FeignContextAutoConfiguration.java │ ├── dubbo/ # Dubbo 支持 │ │ ├── DubboConsumerContextFilter.java │ │ ├── DubboProviderContextFilter.java │ │ └── DubboContextAutoConfiguration.java │ ├── grpc/ # gRPC 支持 │ │ ├── GrpcClientUserContextInterceptor.java │ │ ├── GrpcServerUserContextInterceptor.java │ │ └── GrpcContextAutoConfiguration.java │ ├── resttemplate/ # RestTemplate 支持 │ │ ├── RestTemplateUserContextInterceptor.java │ │ └── RestTemplateContextAutoConfiguration.java │ └── webclient/ # WebClient 支持 │ ├── WebClientUserContextFilter.java │ └── WebClientContextAutoConfiguration.java ├── util/ │ ├── UserContext.java # 用户上下文工具类 │ └── Result.java # 统一响应结果 └── resources/ └── META-INF/ ├── spring/*.imports # Spring Boot 3 自动装配 └── dubbo/*.Filter # Dubbo SPI 配置 ``` **支持的 RPC 框架:** | 框架 | 传输载体 | 自动启用条件 | |------|----------|-------------| | Feign | HTTP Header | classpath 有 `spring-cloud-starter-openfeign` | | Dubbo | RpcContext Attachment | classpath 有 `dubbo-spring-boot-starter` | | gRPC | Metadata | classpath 有 `grpc-spring-boot-starter` | | RestTemplate | HTTP Header | classpath 有 `spring-boot-starter-web` | | WebClient | HTTP Header | classpath 有 `spring-boot-starter-webflux` | **使用方式:** 只需引入 common 依赖 + 对应 RPC 框架依赖,用户上下文会自动传递: ```xml com.account common 1.0.0 org.apache.dubbo dubbo-spring-boot-starter 3.2.10 org.springframework.cloud spring-cloud-starter-openfeign ``` **UserContext:** ```java public class UserContext { public static String getUsername() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); return auth != null ? auth.getName() : null; } public static List getRoles() { return getAuthorities().stream() .map(GrantedAuthority::getAuthority) .filter(a -> a.startsWith("ROLE_")) .collect(Collectors.toList()); } public static boolean hasPermission(String permission) { return getPermissions().contains(permission); } } ``` --- ## 四、安全模型 ### 4.1 RBAC 权限模型 ``` 用户(User) ──→ 角色(Role) ──→ 权限(Permission) │ └──→ 数据范围(DataScope) - ALL: 全部数据 - SELF: 仅自己数据 ``` ### 4.2 权限编码规范 ``` 角色编码:ROLE_ 前缀 - ROLE_SUPER_ADMIN 超级管理员 - ROLE_ADMIN 管理员 - ROLE_USER 普通用户 权限编码:资源:操作 - goods:list 商品列表 - goods:add 新增商品 - goods:edit 编辑商品 - goods:delete 删除商品 ``` ### 4.3 鉴权方式 | 层级 | 方式 | 实现 | |------|------|------| | 网关层 | JWT 校验 | JwtAuthFilter | | URL 层 | 动态权限匹配 | UrlPermissionFilter + PermissionCache | | 方法层 | 注解鉴权 | @PreAuthorize | | 数据层 | 数据过滤 | Service 层判断 | ### 4.4 动态权限机制 **权限缓存(PermissionCache):** 服务启动时从数据库加载 `URL+Method → 权限标识` 映射,缓存到本地: ```java @Component public class PermissionCache { // 缓存:key = "GET:/goods/list", value = "goods:list" private final Map permissionMap = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @PostConstruct public void init() { refresh(); // 启动时加载 } public void refresh() { lock.writeLock().lock(); try { permissionMap.clear(); List permissions = permissionMapper.selectList(null); for (SysPermission perm : permissions) { String key = perm.getMethod() + ":" + perm.getUrl(); permissionMap.put(key, perm.getPermCode()); } } finally { lock.writeLock().unlock(); } } } ``` **URL 级动态鉴权(UrlPermissionFilter):** ```java @Component public class UrlPermissionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(...) { String method = request.getMethod(); String uri = request.getRequestURI(); // 1. 查询该 URL 需要的权限 String requiredPermission = permissionCache.getRequiredPermission(method, uri); // 2. 未配置则放行(由 @PreAuthorize 兜底) if (requiredPermission == null) { filterChain.doFilter(request, response); return; } // 3. 校验用户是否有该权限 Set userPermissions = getUserPermissions(); if (!userPermissions.contains(requiredPermission)) { sendForbidden(response, "权限不足"); return; } filterChain.doFilter(request, response); } } ``` **权限刷新接口:** 当数据库中权限规则变更时,调用接口刷新缓存,无需重启服务: ```bash # 刷新下游服务的权限缓存 POST /admin/permission/refresh ``` **动态权限更新流程:** ``` 1. 管理员在 admin-service 修改权限规则 2. 调用 POST /admin/permission/refresh 3. admin-service 通知 business-service 刷新缓存 4. business-service 重新从数据库加载权限映射 5. 新规则立即生效,无需重启 ``` --- ## 五、快速开始 ### 5.1 环境准备 ```bash # 启动 MySQL docker run -d --name rbac-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=rbac_demo \ mysql:8.0 # 初始化数据库 mysql -uroot -proot < sql/init.sql mysql -uroot -proot < sql/data.sql ``` ### 5.2 启动服务 ```bash # 1. 安装公共模块 cd common && mvn install # 2. 启动各服务(按顺序) cd auth-service && mvn spring-boot:run cd admin-service && mvn spring-boot:run cd business-service && mvn spring-boot:run cd gateway-service && mvn spring-boot:run ``` ### 5.3 测试接口 ```bash # 1. 登录获取 Token curl -X POST http://localhost:8080/auth/oauth2/token \ -d "grant_type=password&username=admin&password=123456" # 2. 访问受保护接口 curl http://localhost:8080/api/goods/list \ -H "Authorization: Bearer " ``` --- ## 六、常见问题 ### Q1: 为什么网关不能直接把用户信息存 ThreadLocal? ThreadLocal 是线程隔离的,只在当前进程内有效。网关和下游服务是不同的进程,无法共享内存,只能通过 HTTP Header 传递。 ### Q2: 为什么下游服务要用 ThreadLocal? 如果不用 ThreadLocal,每个方法都要传 UserDTO 参数,代码会很臃肿。存入 ThreadLocal 后,任何地方都能通过 `UserContext.getUsername()` 获取。 ### Q3: ThreadLocal 会内存泄漏吗? 会。线程池中的线程会被复用,如果不清理 ThreadLocal,下一个请求可能读到上一个请求的数据。所以 Filter 的 finally 块必须调用 `SecurityContextHolder.clearContext()`。 ### Q4: 服务间调用怎么传递用户信息? common 模块已内置多种 RPC 框架的拦截器,引入对应依赖即可自动启用: **Feign(HTTP):** ```java // 自动注册,无需手动配置 @FeignClient("business-service") public interface BusinessClient { @GetMapping("/goods/list") Result> listGoods(); // 用户信息自动传递 } ``` **Dubbo(RPC):** ```java // 自动注册,无需手动配置 @DubboReference private GoodsService goodsService; public void test() { goodsService.listGoods(); // 用户信息通过 RpcContext 自动传递 } ``` **gRPC:** ```java // 自动注册,无需手动配置 @GrpcClient("business-service") private GoodsServiceGrpc.GoodsServiceBlockingStub stub; public void test() { stub.listGoods(request); // 用户信息通过 Metadata 自动传递 } ``` **传递的 Header/Attachment:** | Key | 说明 | |-----|------| | X-User-Name | 用户名 | | X-User-Id | 用户ID | | X-User-Roles | 角色列表(逗号分隔) | | X-User-Permissions | 权限列表(逗号分隔) | | X-Data-Scope | 数据范围(ALL/SELF) | --- ## 七、项目结构 ``` ├── common/ # 公共模块(多 RPC 框架支持) │ ├── context/ │ │ ├── UserContextHolder.java │ │ └── propagation/ │ │ └── ContextPropagator.java │ ├── filter/ │ │ └── GatewayAuthFilter.java │ ├── rpc/ │ │ ├── feign/ # Feign 拦截器 │ │ ├── dubbo/ # Dubbo Filter │ │ ├── grpc/ # gRPC 拦截器 │ │ ├── resttemplate/ # RestTemplate 拦截器 │ │ └── webclient/ # WebClient 过滤器 │ └── util/ │ ├── UserContext.java │ └── Result.java │ ├── auth-service/ # 认证服务 :9001 │ ├── entity/ │ ├── mapper/ │ ├── service/ │ └── util/ │ ├── SecurityConfig.java │ ├── OAuthConfig.java │ └── JwtUtil.java │ ├── gateway-service/ # 网关服务 :8080 │ ├── filter/ │ │ └── JwtAuthFilter.java │ ├── config/ │ │ └── CorsConfig.java │ └── util/ │ └── JwtUtil.java │ ├── admin-service/ # 管理服务 :9002 │ ├── entity/ │ ├── mapper/ │ ├── service/ │ ├── controller/ │ ├── filter/ │ │ └── GatewayAuthFilter.java │ ├── security/ │ │ ├── PermissionCache.java │ │ └── UrlPermissionFilter.java │ └── config/ │ └── SecurityConfig.java │ └── business-service/ # 业务服务 :9003 ├── entity/ ├── mapper/ ├── service/ ├── controller/ │ ├── GoodsController.java │ └── InternalController.java # 内部接口(权限刷新) ├── filter/ │ └── GatewayAuthFilter.java ├── security/ │ ├── PermissionCache.java # 权限缓存 │ └── UrlPermissionFilter.java # URL级动态鉴权 └── config/ └── SecurityConfig.java ``` --- ## 八、设计原则 ### 8.1 职责单一 - **网关只认证,不鉴权**:校验 JWT 有效性,透传用户信息 - **微服务只鉴权,不重复校验 JWT**:信任网关透传的 Header ### 8.2 无状态设计 - JWT 自带用户信息和权限,服务端无需存储会话 - 便于水平扩展,任意节点都能处理请求 ### 8.3 动态可配置 - 权限规则存储在数据库,支持实时更新 - 调用刷新接口即可生效,无需修改代码和重启服务 --- ## 九、避坑指南 ### 9.1 密钥一致性 认证服务和网关的 JWT 签名密钥必须相同,否则网关校验令牌会失败。 ```yaml # auth-service 和 gateway-service 的 application.yml jwt: secret: your-256-bit-secret-key-for-jwt-signing-must-be-at-least-32-chars ``` ### 9.2 权限标识匹配 JWT 中的权限标识需与微服务鉴权规则中的标识完全一致: ```java // 数据库中存的是 goods:list @PreAuthorize("hasAuthority('goods:list')") // ✅ 正确 // hasRole 会自动拼接 ROLE_ 前缀 @PreAuthorize("hasRole('ADMIN')") // 实际匹配 ROLE_ADMIN ``` ### 9.3 缓存线程安全 微服务加载权限规则时需加锁,避免多线程并发修改缓存导致规则混乱: ```java private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void refresh() { lock.writeLock().lock(); // 写锁 try { // 刷新缓存 } finally { lock.writeLock().unlock(); } } public String getRequiredPermission(String method, String url) { lock.readLock().lock(); // 读锁 try { return permissionMap.get(key); } finally { lock.readLock().unlock(); } } ``` ### 9.4 网络隔离 下游服务应部署在内网,防止绕过网关直接访问: ``` 外网 → 网关(8080) → 内网服务(9001/9002/9003) ↑ 唯一入口 ```