乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

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 并调用 AuthenticationManagerspring-security-oauth2-resource-server
JwtAuthenticationProvider调用 JwtDecoder 解码 Token,再调用 JwtAuthenticationConverter 生成 Authenticationspring-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 集成错误之一。

上一页
JwtDecoder
下一页
JwtAuthenticationConverter