Spring Security如何解析JWT,并自行构造SecurityContex

Spring Security 通过一个精心设计的流程将 JWT 令牌转化为可识别的用户认证信息。其核心在于一个自定义的认证过滤器 ,它负责拦截请求、提取并验证 JWT,最终将认证信息存入 SecurityContext,供后续的授权和业务逻辑使用。

下面这张流程图清晰地展示了这一完整过程,包括关键角色和核心步骤:

🔧 核心组件详解

这个流程依赖于几个关键组件,它们各司其职,共同协作:

  1. JWT认证过滤器 (JwtAuthenticationFilter)​ :这是整个机制的"发动机"。它通常继承自 OncePerRequestFilter,确保每个请求只被处理一次。其核心方法 doFilterInternal完成了流程图中的主要逻辑:提取令牌、验证、构建认证对象并设置上下文。
  2. SecurityContextHolder :这是Spring Security的"会话中心"。它使用 ThreadLocal策略,将 SecurityContext(内含 Authentication对象)与当前处理请求的线程绑定。一旦设置,在本次请求的后续任何环节(如Controller、Service)都能通过 SecurityContextHolder.getContext().getAuthentication()获取到当前用户信息。
  3. Authentication 对象 :这是用户的"身份证"。通常使用 UsernamePasswordAuthenticationToken来创建,其中包含了主体(如用户名)、凭证(JWT模式下通常为null)和权限列表。将这个对象设置到安全上下文中,就等于告诉Spring Security"当前用户已登录"。

💻 代码实现关键步骤

以下是实现这一流程的核心代码环节:

1. 配置无状态会话管理

在安全配置类中,必须声明应用为无状态,这意味着Spring Security不会创建或使用HttpSession。

less 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 关键配置
            )
            // ... 其他配置,如授权规则
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 将自定义过滤器加入链中
        return http.build();
    }
}

2. 实现JWT认证过滤器

过滤器的逻辑是核心,其伪代码如下:

scala 复制代码
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtil jwtUtil; // 自定义JWT工具类
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 1. 从请求头(如Authorization: Bearer <token>)提取JWT
        String jwt = extractJwtFromRequest(request);

        if (jwt != null && jwtUtil.validateToken(jwt)) {
            // 2. 从有效JWT中解析出用户名
            String username = jwtUtil.extractUsername(jwt);
            // 3. 根据用户名加载用户详细信息(包括权限)
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            // 4. 创建Authentication对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            // 5. 将Authentication设置到SecurityContext中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        // 6. 继续执行过滤器链
        chain.doFilter(request, response);
    }
}

⚠️ 注意事项与最佳实践

  • 确保过滤器顺序 :自定义的JWT过滤器必须添加到 UsernamePasswordAuthenticationFilter之前,这样才能在默认的表单登录逻辑介入之前完成JWT认证。
  • 妥善处理异常:在过滤器中,JWT解析或验证失败时应记录日志,但通常不应直接抛出异常中断流程,而是直接放行。请求会因后续没有有效的认证信息而被拒绝,这样可以保证系统的健壮性。
  • 无状态与CSRF防护:由于采用无状态认证,通常会禁用CSRF(跨站请求伪造)防护,因为CSRF防护通常依赖于Session状态。
  • 权限信息存储 :可以考虑将用户角色或权限列表直接作为自定义声明(Claim)存入JWT的Payload中。这样在过滤器中可以直接从JWT解析出权限,无需每次都查询数据库或 UserDetailsService,可以提升性能。
相关推荐
敖正炀7 小时前
Java 线程状态变化与ObjectMonitor之间的关系
jvm·后端
前端付豪7 小时前
Prompt Playground(实现提示词工作台)
前端·人工智能·后端
无籽西瓜a7 小时前
【西瓜带你学设计模式 | 第三期-工厂方法模式】工厂方法模式——定义、实现方式、优缺点与适用场景以及注意事项
java·后端·设计模式·工厂方法模式
谁在黄金彼岸8 小时前
MariaDB Docker容器权限配置问题分析与解决方案
后端·docker·容器
镜花水月linyi8 小时前
Redis 为什么快?
redis·后端
Walter先生8 小时前
实时行情系统设计:从协议选择到高可用架构,再到数据源选型
后端·架构·实时行情数据源
无籽西瓜a8 小时前
【西瓜带你学设计模式 | 第四期 - 抽象工厂模式】抽象工厂模式 —— 定义、核心结构、实战示例、优缺点与适用场景及模式区别
java·后端·设计模式·软件工程·抽象工厂模式
_院长大人_8 小时前
Spring Boot 3.3 + Atomikos 分布式事务日志路径配置踩坑记录
spring boot·分布式·后端
snakeshe10108 小时前
MyBatis 从入门到实践:ORM 核心机制与动态 SQL 全解析
后端
野犬寒鸦8 小时前
高并发利器:SingleFlight优化指南(Java版实现与项目实战)
服务器·开发语言·redis·后端·面试