Spring Boot中基于JWT认证微服务实现对另一微服务的安全防护
问题解答:仅用JWT实现微服务间的认证授权
完全可以仅使用JWT将你的认证微服务(微服务1)作为授权服务器,不需要依赖Zuul/Eureka或完整的OAuth2.0套件,以下是最简实现方案:
一、认证服务(微服务1)核心配置
1. 确保JWT生成逻辑合规
用Spring Security的JwtEncoder生成Token,需包含:
- 用户唯一标识(
subject) - 用户权限列表(自定义Claim,比如
authorities) - 合理的过期时间(比如1小时)
- 用**非对称加密(RSA)**签名(比对称密钥更安全,避免密钥泄露风险)
2. 暴露Token验证接口
提供一个公开的Token验证接口,供微服务2调用,验证Token的有效性并返回用户信息:
@RestController @RequestMapping("/auth") public class AuthController { private final JwtDecoder jwtDecoder; public AuthController(JwtDecoder jwtDecoder) { this.jwtDecoder = jwtDecoder; } @PostMapping("/validate") public ResponseEntity<Map<String, Object>> validateToken(@RequestHeader("Authorization") String tokenHeader) { try { String token = tokenHeader.replace("Bearer ", ""); Jwt jwt = jwtDecoder.decode(token); Map<String, Object> result = new HashMap<>(); result.put("valid", true); result.put("username", jwt.getSubject()); result.put("authorities", jwt.getClaims().get("authorities")); return ResponseEntity.ok(result); } catch (JwtException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(Map.of("valid", false, "message", "Invalid token: " + e.getMessage())); } } }
3. 放开验证接口的匿名访问
在Spring Security配置中允许匿名访问验证接口:
@Configuration @EnableWebSecurity public class AuthSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/login", "/auth/validate").permitAll() .anyRequest().authenticated() ); return http.build(); } // 此处省略JwtEncoder/JwtDecoder的Bean配置(可参考Spring Security官方文档生成RSA密钥对) }
二、资源服务(微服务2)核心配置
1. 实现自定义JWT验证过滤器
在请求到达受保护端点前,提取Token并调用微服务1的验证接口,验证通过后将用户信息存入SecurityContext:
@Component public class JwtValidationFilter extends OncePerRequestFilter { private final RestTemplate restTemplate; // 简单场景下硬编码认证服务地址,集群场景可替换为服务发现(如Eureka) private static final String AUTH_VALIDATE_URL = "http://localhost:8080/auth/validate"; public JwtValidationFilter(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String tokenHeader = request.getHeader("Authorization"); if (tokenHeader == null || !tokenHeader.startsWith("Bearer ")) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid Authorization header"); return; } try { // 调用认证服务验证Token HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", tokenHeader); HttpEntity<Void> requestEntity = new HttpEntity<>(headers); ResponseEntity<Map> validateResponse = restTemplate.exchange( AUTH_VALIDATE_URL, HttpMethod.POST, requestEntity, Map.class ); if (!validateResponse.getStatusCode().is2xxSuccessful() || !(boolean) validateResponse.getBody().get("valid")) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token"); return; } // 构建Authentication对象并存入SecurityContext List<String> authorityStrings = (List<String>) validateResponse.getBody().get("authorities"); Collection<GrantedAuthority> authorities = authorityStrings.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); Authentication auth = new UsernamePasswordAuthenticationToken( validateResponse.getBody().get("username"), null, authorities ); SecurityContextHolder.getContext().setAuthentication(auth); filterChain.doFilter(request, response); } catch (Exception e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token validation failed: " + e.getMessage()); } } // 跳过公开接口的验证 @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return request.getRequestURI().startsWith("/public/"); } }
2. 配置Spring Security启用过滤器
将自定义过滤器注册到Spring Security链中,保护指定端点:
@Configuration @EnableWebSecurity public class ResourceSecurityConfig { private final JwtValidationFilter jwtValidationFilter; public ResourceSecurityConfig(JwtValidationFilter jwtValidationFilter) { this.jwtValidationFilter = jwtValidationFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtValidationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
三、优化建议
- 本地验证替代远程调用:微服务1暴露公钥接口(如
GET /auth/public-key),微服务2拉取公钥后本地解析Token,避免远程调用的性能开销和依赖风险。 - 服务发现:如果微服务是集群部署,用Eureka/Nacos替换硬编码的认证服务地址,提高可用性。
- Token刷新机制:实现Token刷新接口,避免用户频繁登录。
- 权限细粒度控制:用Spring Security的
@PreAuthorize注解在方法级别控制权限。
内容的提问来源于stack exchange,提问作者Iheb RIAHI
相关产品推荐
相关产品推荐

