JWT 认证过滤器实战(手写或 spring-security-oauth2-resource-server 思路)

表单登录靠 Session 存状态,前后端分离/微服务下更常用 JWT:服务端签名发令牌,客户端每次带 Authorization: Bearer xxx,服务端验签即认人,天然无状态、可横向扩容。JWT 由三段 base64 组成:Header.Payload.Signature(头、载荷、签名)。本章给两条落地路线(Boot 3 主线)。

路线 A:手写 OncePerRequestFilter(教学/理解链路)

过滤器只做一件事:解析令牌、校验通过则把 Authentication 塞进 SecurityContext。

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;     // 签发与校验工具:验签、过期、取用户名

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp,
                                    FilterChain chain) throws ServletException, IOException {
        String header = req.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (jwtUtil.validate(token)) {
                var auth = new UsernamePasswordAuthenticationToken(
                        jwtUtil.parseUsername(token), null,
                        List.of(new SimpleGrantedAuthority("ROLE_USER")));
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(req, resp);     // 无令牌也放行,交给授权过滤器决定 401
    }
}

把它挂到链上(在用户名密码过滤器之前执行):

http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
    .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

签发侧:密钥(HS256 需 ≥256 bit)、过期时间、claims 放用户信息;签名库可选手写封装或成熟库(如 Nimbus、jjwt),实现细节以所选库文档为准。验签失败/过期要给出 401(见第 33 章异常处理)。

路线 B:spring-security-oauth2-resource-server(生产推荐)

手写过滤器容易在签名校验、密钥轮换、claim 转换上出错。资源服务器场景直接交给框架:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com   # 自动抓取 JWK 公钥并校验 iss/exp/签名
          # 或 jwk-set-uri: https://auth.example.com/.well-known/jwks.json
http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
    .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
    .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

框架用 Nimbus 自动完成验签、过期与颁发者校验,并把 JWT claims 转成 Authentication。需要自定义"角色从哪个 claim 取"时提供 JwtAuthenticationConverter Bean。

对称密钥的内部演示场景,可自定义 JwtDecoder:

@Bean
public JwtDecoder jwtDecoder(@Value("${app.jwt.secret}") String secret) {
    SecretKey key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    return NimbusJwtDecoder.withSecretKey(key).build();   // 签名算法 API 演进以官方文档为准
}

注意事项

  • 密钥必须安全存放(环境变量/配置中心,别提交仓库),传输走 HTTPS;
  • 令牌有效期设短(如 30 分钟),配合 refresh token 续期;
  • 无状态意味着服务端无法主动踢人:紧急下线靠黑名单/短过期/改密钥,权衡取舍以官方文档为准;
  • 生产多服务共享登录态时,建议统一认证中心(OIDC)发令牌,各服务只做资源服务器。

小结:理解链路用手写过滤器,生产推荐 oauth2-resource-server;核心是"验签 + 过期 + 注入 SecurityContext",并做好密钥管理与短时效设计。

笔记加载中…