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",并做好密钥管理与短时效设计。