You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 本地验证替代远程调用:微服务1暴露公钥接口(如GET /auth/public-key),微服务2拉取公钥后本地解析Token,避免远程调用的性能开销和依赖风险。
  2. 服务发现:如果微服务是集群部署,用Eureka/Nacos替换硬编码的认证服务地址,提高可用性。
  3. Token刷新机制:实现Token刷新接口,避免用户频繁登录。
  4. 权限细粒度控制:用Spring Security的@PreAuthorize注解在方法级别控制权限。

内容的提问来源于stack exchange,提问作者Iheb RIAHI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:15:07