Spring Cloud实现权限管理(网关+jwt版)
首先要想一个问题:为什么微服务不能像普通的Spring Boot项目一样鉴权?其实并不是不能,而是不适合。 在微服务架构中使用 Spring Security + RBAC/JET 时,会面临 "重复鉴权" 的核心痛点。下面从技术原理到解决方案完整解析这个问题。
问题本质:为什么会有重复鉴权?
传统单体架构流程
• 所有权限校验集中在一个应用内完成 • 用户登录后,Session或Token在单应用中全局有效
微服务架构下的流程
• 每个微服务都需要独立完成:
解析Token
查询数据库验证权限
构建SecurityContext
假设有3个服务链式调用:
客户端 → 网关 → 服务A → 服务B → 服务C 每个服务都重复: 查询用户数据(1次DB查询) 查询权限数据(1次DB查询)总查询次数 = 3服务 × 2查询 = 6次• 在服务交叉的时候,导致 多次数据库查询 和 重复计算,使数据库压力倍增和网络开销叠加。
(1)创建 JWT 工具类
在util模块下创建JWT 工具类
@Component public class JwtTokenUtil { // 密钥,用于签名和验证 JWT,应妥善保管 @Value("${jwt.secret}") private String secret; // JWT 的过期时间,这里设置为 10 小时 @Value("${jwt.expiration}") private Long expiration; // 根据用户详细信息生成 JWT public String generateToken(SysRoleNameUserId sysRoleNameUserId) { //自定义的声明 Map<String, Object> claims = new HashMap<>(); //鉴权所需的权限角色 claims.put("identities",sysRoleNameUserId.getRoleNames()); return createToken(claims, String.valueOf(sysRoleNameUserId.getUserId())); } // 创建 JWT 的具体方法 private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims)//自定义的声明 .setSubject(subject)//存的用户id .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } //解析jwt public Claims extractAllClaims(String token) { return Jwts.parserBuilder() .setSigningKey(secret) .build() .parseClaimsJws(token) .getBody(); } }
JWT 的密钥(jwt.secret)应通过配置中心(如 Nacos)或环境变量注入,避免硬编码。这里选择环境变量注入: 在util模块下的application.yml
配置文件里面指定
jwt: secret: your-secret-key-here-must-be-at-least-256-bits-long expiration: 1000 * 60 * 60 * 10 # 10 hour
(2)创建JwtFilter
在gateway网关模块创建JwtFilter过滤器来验证并解析jwt,从而获取权限信息
@Component public class JwtFilter implements GlobalFilter, Ordered { @Resource private JwtTokenUtil jwtTokenUtil; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); List<String> authHeader = headers.get("Authorization"); if (authHeader != null && authHeader.get(0).startsWith("Bearer ")) { String token = authHeader.get(0).substring(7); // 验证 JWT 并提取角色信息 Claims claims = null; try{ claims = jwtTokenUtil.extractAllClaims(token); }catch (IllegalArgumentException e) { //解析 JWT 时发生其他错误 System.out.println("解析 token 时发生其他错误"); } catch (ExpiredJwtException e) { //JWT 已过期 System.out.println("token 已过期"); } //取出权限角色列表 Object identitiesObj = claims.get("identities"); List<GrantedAuthority> authorities = new ArrayList<>(); if (identitiesObj instanceof List<?>) { for (Object role : (List<?>) identitiesObj) { if (role instanceof String) { // 将字符串转换为 SimpleGrantedAuthority authorities.add(new SimpleGrantedAuthority((String) role)); } } } String userId = claims.getSubject(); //将 Authentication 对象设置到 SecurityContextHolder 中后,Spring Security 就能在后续的授权过程中使用这些权限信息了。 Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); } return chain.filter(exchange); } @Override public int getOrder() { return -1; } }
(3)配置 Spring Security
在网关gateway模块配置所有微服务整体的权限管理规则:
@Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http.csrf().disable()// 关闭 CSRF(跨站请求伪造)防护。在无状态 API(如 JWT 场景)中,CSRF 防护不必要(通常依赖 Authorization 头而非 Cookie)。 避免对 POST、PUT 等请求要求携带 CSRF Token。 .authorizeExchange() .pathMatchers("/api/product/**").permitAll() .pathMatchers("/admin/**").permitAll() .anyExchange().authenticated(); return http.build(); } }
如果各个微服务还需要独自的更细粒度的权限控制,只需要在单个微服务模块中单独配置一个Spring Security就行了。