JWT 认证
定义与作用
JWT 认证是指 Spring Security 以 OAuth2 Resource Server 角色接入系统,通过解析和验证 JWT 完成用户身份识别与权限判断的整个过程。它是实现无状态(Stateless)架构的核心手段——服务端不保存任何会话信息,仅依靠客户端提交的 JWT 自包含凭证完成认证。
与传统 Session 认证相比,JWT 认证在 Spring Security 中的本质变化是:
- 认证信息从服务端 Session 存储转移到客户端 Token 携带
- 过滤器链中不再依赖
SecurityContextHolderFilter从 Session 恢复SecurityContext - 新增
BearerTokenAuthenticationFilter从 HTTP 请求头中提取 Token 并触发认证
核心原理
过滤器链中的 JWT 认证
当 oauth2ResourceServer().jwt() 启用后,Spring Security 在过滤器链中注册 BearerTokenAuthenticationFilter(通常位于 BasicAuthenticationFilter 之后):
关键组件协作
| 组件 | 职责 | 所属模块 |
|---|---|---|
BearerTokenAuthenticationFilter | 从请求头提取 Bearer Token,封装为 BearerTokenAuthenticationToken 并调用 AuthenticationManager | spring-security-oauth2-resource-server |
JwtAuthenticationProvider | 调用 JwtDecoder 解码 Token,再调用 JwtAuthenticationConverter 生成 Authentication | spring-security-oauth2-resource-server |
JwtDecoder | 解码 JWT 并验证签名、exp、nbf 等 | spring-security-oauth2-jose |
JwtAuthenticationConverter | 将 Jwt 中的 Claims 转换为 Spring Security 的 GrantedAuthority 列表 | spring-security-oauth2-resource-server |
必须配置:STATELESS 会话策略
启用 JWT 认证时,务必显式禁用 Session 创建,否则 Spring Security 仍会为每个请求创建 Session,违背无状态设计:
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
示例一:保护 REST API 的基础 JWT 配置
场景:一个订单管理系统的 REST API,要求所有 /api/** 请求必须携带有效 JWT,公共接口除外。
操作前配置(无安全保护):
@EnableWebSecurity
public class OpenApiConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").permitAll();
}
}
问题:任何人可直接调用 /api/orders,订单数据暴露。
操作后配置(JWT 保护):
@EnableWebSecurity
public class JwtResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 无状态 API 通常禁用 CSRF
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/orders").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.POST, "/api/orders").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/api/orders/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
application.yml 配置:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth-server.example.com
jwk-set-uri: https://auth-server.example.com/.well-known/jwks.json
结果分析:
BearerTokenAuthenticationFilter拦截所有/api/**请求,检查Authorization: Bearer <token>头- 无 Token 或 Token 无效时返回 401(
WWW-Authenticate: Bearer) - Token 有效但权限不足时返回 403
SessionCreationPolicy.STATELESS确保不创建 HttpSession,减少服务端内存占用
测试验证:
# 未携带 Token → 401
curl -i http://localhost:8080/api/orders
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Bearer
# 携带有效 Token → 200
curl -i http://localhost:8080/api/orders \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
# HTTP/1.1 200 OK
示例二:混合架构——JWT 无状态 API + Session 表单登录
场景:一个系统同时提供 Web 管理后台(表单登录 + Session)和 REST API(JWT 无状态),需要两套认证机制共存。
操作前配置(仅支持表单登录):
@EnableWebSecurity
public class MixedSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/css/**", "/js/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/admin/dashboard");
}
}
问题:/api/** 使用 Session 认证,前端(SPA / 移动端)需处理 Cookie 和 CSRF,前后端分离体验差。
操作后配置(多 SecurityFilterChain 共存):
在 Spring Security 5.x 中,通过多个 WebSecurityConfigurerAdapter 以 @Order 控制优先级,实现不同路径的不同认证策略:
@Configuration
@Order(1)
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/css/**", "/js/**", "/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/admin/dashboard")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(false);
}
}
结果分析:
@Order(1)的ApiSecurityConfig优先处理/api/**,完全无状态,不创建 Session@Order(2)的WebSecurityConfig处理剩余路径,使用传统表单登录 + Session- SPA 前端访问
/api/**时在 Header 携带 JWT,不受 Cookie 和 CSRF 约束 - Web 后台用户通过浏览器表单登录,享受 Session 持久化便利
- 两套机制独立工作,互不干扰
易错场景:忘记设置 STATELESS 导致 Session 污染
面试高频考点
错误现象:配置了 oauth2ResourceServer().jwt(),但应用仍创建大量 Session,内存占用居高不下,且出现奇怪的认证状态错乱。
错误配置:
@EnableWebSecurity
public class WrongConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
// 遗漏了 sessionManagement() 配置!
}
}
问题分析:
- Spring Security 默认会话策略为
IF_REQUIRED,即按需创建 Session BearerTokenAuthenticationFilter认证成功后,默认的SecurityContextRepository(基于 Session)会将SecurityContext存入 HttpSession- 后续请求即使携带了 JWT,过滤器也可能先从 Session 恢复旧的 SecurityContext,导致:
- JWT 已过期但 Session 未过期,用户仍能访问(安全漏洞)
- JWT 权限已更新但 Session 中仍是旧权限,权限不同步
正确配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 必须显式声明
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
核心要点:JWT 无状态认证必须搭配 SessionCreationPolicy.STATELESS,切断 Session 与 SecurityContext 的关联,确保每次请求都独立校验 JWT。遗漏此配置是生产环境最常见的 JWT 集成错误之一。