Spring Boot + JWT:如何在其他微服务中复用令牌?
解决方案:网关统一鉴权后传递角色信息,后端服务直接复用权限控制
核心思路
网关通过auth-service完成JWT的有效性验证后,直接解析令牌中的角色信息,通过可信的自定义请求头传递给X、Y服务;后端服务无需重复验证JWT,仅需提取请求头中的角色信息构建认证上下文,即可直接使用@PreAuthorize("hasRole('ROLE_ADMIN')")这类注解做权限控制。
具体实现步骤
1. 网关层:验证JWT并传递角色信息
以Spring Cloud Gateway为例,实现全局过滤器完成JWT验证、角色解析与请求头注入:
@Component public class JwtAuthFilter implements GlobalFilter, Ordered { @Autowired private AuthServiceClient authServiceClient; // 调用auth-service的客户端 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String jwtToken = authHeader.substring(7); // 调用auth-service验证令牌有效性并获取角色列表 AuthValidateResult result = authServiceClient.validateAndParseToken(jwtToken); if (result.isValid()) { // 将角色列表以自定义请求头传递给后端服务,多角色用逗号分隔 ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() .header("X-User-Roles", String.join(",", result.getRoles())) .build(); return chain.filter(exchange.mutate().request(modifiedRequest).build()); } else { // 验证失败返回401 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } // 无有效令牌返回401 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } @Override public int getOrder() { return -100; // 确保该过滤器优先级最高,先于路由转发执行 } }
关键防护:在网关路由规则中添加断言,拒绝外部请求携带X-User-Roles头,防止伪造:
spring: cloud: gateway: routes: - id: x-service-route uri: lb://x-service predicates: - Path=/x/** - Header=X-User-Roles, !.* # 拒绝外部请求带该头
2. X/Y服务:提取角色并配置权限控制
配置Spring Security,通过自定义过滤器从请求头提取角色,构建已认证上下文:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级权限控制 public class ServiceSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() // 添加自定义过滤器,在用户名密码认证前执行 .addFilterBefore(new RolesHeaderAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // 自定义过滤器:从请求头提取角色,构建认证信息 private class RolesHeaderAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String rolesStr = request.getHeader("X-User-Roles"); if (rolesStr != null) { // 将角色字符串转为权限对象列表 List<GrantedAuthority> authorities = Arrays.stream(rolesStr.split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // 构建已认证的Authentication对象,存入安全上下文 Authentication auth = new UsernamePasswordAuthenticationToken( request.getHeader("X-User-Id"), // 可同时传递用户ID等其他信息 null, authorities ); SecurityContextHolder.getContext().setAuthentication(auth); } chain.doFilter(request, response); } } }
3. 方法级权限控制使用
在X/Y服务的业务方法上直接使用注解:
@RestController @RequestMapping("/x") public class XController { // 仅管理员可访问 @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/admin/operation") public String adminOnlyOperation() { return "Admin operation executed"; } // 普通用户可访问 @PreAuthorize("hasRole('ROLE_USER')") @GetMapping("/user/operation") public String userOperation() { return "User operation executed"; } }
注意事项
- 网关与后端服务之间的通信需做隔离(如内部网络访问)或TLS加密,防止请求头被篡改
- 若JWT中包含用户ID、昵称等其他信息,可通过相同方式添加自定义请求头传递
- 可根据实际需求扩展网关过滤器,支持令牌过期自动刷新等逻辑
内容的提问来源于stack exchange,提问作者jugor
相关产品推荐
相关产品推荐

