Spring Security JWT 鉴权链路完整解析

 更新时间:2026年02月03日 09:08:19   作者:知识即是力量ol  
文章详细解析了SpringSecurity结合JWT实现鉴权的完整链路,从配置到代码,涵盖了从请求到业务层的每一个步骤,包括配置资源服务器模式、JWT编解码、自定义JWT服务、请求过滤和Controller层的注入机制

一次完整的 Spring Security JWT 鉴权链路解析

在实际项目中,我们经常会在 Controller 里写出这样一个方法签名:

@GetMapping("/me")
public AuthUserResponse me(@AuthenticationPrincipal Jwt jwt) {
    long userId = jwtService.extractUserId(jwt);
    return authService.me(userId);
}

看起来 Spring 能“凭空”把一个 Jwt 对象塞进方法参数里,还自动从请求头里的 Authorization: Bearer 提取 Token 并完成校验。

这篇文章就结合项目,从配置代码,串起这条鉴权链路的每一步。

一、整体流程总览

从前端调用 /api/v1/auth/me 开始,到方法参数里拿到 Jwt jwt,整个链路可以概括为:

  1. 前端发请求:HTTP 请求头里带 Authorization: Bearer <accessToken>
  2. Security 过滤器拦截SecurityFilterChain 中的 Bearer Token 过滤器从请求头里截出 <accessToken>
  3. 交给 JwtDecoder 校验解析:过滤器调用配置好的 JwtDecoder(基于 RSA 公钥的 NimbusJwtDecoder),做签名、过期等校验,解析出 Claim,得到一个 Jwt 对象。
  4. 封装 Authentication 放入 SecurityContext:框架把 Jwt 封装成 JwtAuthenticationToken,写入当前线程的 SecurityContext
  5. Controller 使用 @AuthenticationPrincipal Jwt 注入当前用户 Token:@AuthenticationPrincipalSecurityContext 里取出 principal(类型为 Jwt)注入到方法参数。
  6. 业务层解析用户 IDjwtService.extractUserId(jwt) 从 Claim 中读取 uid,实现当前登录用户识别与后续业务处理。

下面按照“配置层 → 过滤器层 → 控制器层”的顺序,一步步展开。

二、开启资源服务器模式:SecurityFilterChain的核心配置

项目中 Spring Security 的入口配置在 SecurityConfig 中:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            .csrf(AbstractHttpConfigurer::disable)
            .cors(Customizer.withDefaults())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/actuator/health", "/actuator/info").permitAll()
                    // 公开内容:首页 Feed 不需要登录
                    .requestMatchers("/api/v1/knowposts/feed").permitAll()
                    // 知文详情等其他白名单接口...
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
    return http.build();
}

这里有几个关键点:

  • 无状态会话SessionCreationPolicy.STATELESS
    • 服务端不使用 Session 记住“谁登录过”,每个请求都必须自带 Token。
  • 访问控制
    • 白名单接口使用 permitAll() 直接放行;
    • 其他接口通过 .anyRequest().authenticated() 强制要求认证。
  • 启用 JWT 资源服务器模式oauth2ResourceServer(oauth -> oauth.jwt())
    • 告诉 Spring Security 本应用是一个 OAuth2 Resource Server
    • 请求需要通过 Bearer Token + JWT 的方式来进行认证;
    • 框架自动往过滤器链里加上 Bearer Token 相关的过滤器和认证器。

这样一来,我们就不需要自己在每个接口里手动解析请求头,整个 Token 解析与校验流程都由 Spring Security 统一接管。

三、JWT 编解码 Bean:JwtEncoder与JwtDecoder

要让资源服务器真正“识别”JWT,我们必须告诉它如何签发和校验 Token,这部分逻辑集中在 AuthConfiguration 中:

@Configuration
@EnableConfigurationProperties(AuthProperties.class)
@RequiredArgsConstructor
public class AuthConfiguration {
    private final AuthProperties properties;
    @Bean
    public JwtEncoder jwtEncoder() {
        AuthProperties.Jwt jwtProps = properties.getJwt();
        RSAPrivateKey privateKey = PemUtils.readPrivateKey(jwtProps.getPrivateKey());
        RSAPublicKey publicKey = PemUtils.readPublicKey(jwtProps.getPublicKey());
        RSAKey jwk = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(jwtProps.getKeyId())
                .build();
        JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }
    @Bean
    public JwtDecoder jwtDecoder() {
        AuthProperties.Jwt jwtProps = properties.getJwt();
        RSAPublicKey publicKey = PemUtils.readPublicKey(jwtProps.getPublicKey());
        return NimbusJwtDecoder.withPublicKey(publicKey).build();
    }
}
  • JwtEncoder
    • 读取配置中的 RSA 私钥、公钥与 keyId;
    • 构造基于 Nimbus 的 JwtEncoder
    • 用于登录/注册成功后签发 Access Token 与 Refresh Token。
  • JwtDecoder
    • 只读取 RSA 公钥;
    • 创建 NimbusJwtDecoder,用于校验签名、过期时间等信息并解析 Claim。

关键点
一旦容器中存在一个 JwtDecoder Bean,oauth2ResourceServer().jwt() 会自动使用它来完成 JWT 的解析与验证,无需手动再把 JwtDecoder 绑定到过滤器上。

四、自定义 JWT 服务:签发与解析的业务封装JwtService

在业务层,我们通过 JwtService 把 Token 的签发与解析从 Controller 和 Service 中抽离出来:

@Service
@RequiredArgsConstructor
public class JwtService {
    private static final String CLAIM_TOKEN_TYPE = "token_type";
    private static final String CLAIM_USER_ID = "uid";
    private final JwtEncoder jwtEncoder;
    private final JwtDecoder jwtDecoder;
    private final AuthProperties properties;
    private final Clock clock = Clock.systemUTC();
    public TokenPair issueTokenPair(User user) {
        String refreshTokenId = UUID.randomUUID().toString();
        Instant issuedAt = Instant.now(clock);
        Instant accessExpiresAt = issuedAt.plus(properties.getJwt().getAccessTokenTtl());
        Instant refreshExpiresAt = issuedAt.plus(properties.getJwt().getRefreshTokenTtl());
        String accessToken = encodeToken(user, issuedAt, accessExpiresAt, "access", UUID.randomUUID().toString());
        String refreshToken = encodeRefreshToken(user, issuedAt, refreshExpiresAt, refreshTokenId);
        return new TokenPair(accessToken, accessExpiresAt, refreshToken, refreshExpiresAt, refreshTokenId);
    }
    public Jwt decode(String token) {
        return jwtDecoder.decode(token);
    }
    public long extractUserId(Jwt jwt) {
        Object claim = jwt.getClaims().get(CLAIM_USER_ID);
        if (claim instanceof Number number) {
            return number.longValue();
        }
        if (claim instanceof String text) {
            return Long.parseLong(text);
        }
        throw new IllegalArgumentException("Invalid user id in token");
    }
}

在这里:

  • 签发阶段
    • 使用 JwtEncoder 构造带有 uidtoken_typejti 等 Claim 的 Token;
    • Access Token 与 Refresh Token 使用不同的 TTL 与类型标记。
  • 解析阶段
    • 可以通过 decode() 手动解析某个 Token(例如刷新或登出时用);
    • 对于 /me 等需要“当前用户”的接口,更多使用 extractUserId(jwt),从已经被框架解析过的 Jwt 中读取 uid Claim。

五、请求进入时:Filter 如何一步步完成 JWT 鉴权

当一个请求携带:

  • GET /api/v1/auth/me
  • Authorization: Bearer <accessToken>

到达服务端时,Spring Security 会按如下步骤进行处理:

  • 进入 SecurityFilterChain
    • 请求首先会进入配置好的安全过滤器链;
    • 因为启用了 .oauth2ResourceServer().jwt(),链中包含 Bearer Token 相关过滤器。
    • 从请求头中提取 Bearer Token
    • 过滤器内部使用 BearerTokenResolver(默认实现为 DefaultBearerTokenResolver):
    • 从请求头里读取 Authorization
  • 匹配 Bearer 前缀;
    • 截取出 <accessToken> 部分。
    • 创建未认证的 Authentication 并委托给 AuthenticationManager
    • 根据 Token 创建一个 BearerTokenAuthenticationToken,此时它还处于“未认证”状态;
    • 将该对象交由 AuthenticationManager(内部委托给 JwtAuthenticationProvider)处理。
    • JwtAuthenticationProvider 使用 JwtDecoder 进行校验与解析
    • JwtAuthenticationProvider 注入的就是我们前面定义的 JwtDecoder Bean;
  • 调用 jwtDecoder.decode(accessToken)
    • 使用 RSA 公钥校验签名;
    • 校验 Token 是否过期、是否生效(exp/nbf 等);
    • 解析出完整的 Claim 集合并构造 Jwt 对象。
    • 构造认证后的 JwtAuthenticationToken,写入 SecurityContext
    • 若校验通过,JwtAuthenticationProvider 会创建一个认证成功的 JwtAuthenticationToken
  • principal:解析得到的 Jwt
    • authorities:可根据 Claim 映射出的权限列表(本项目中暂未做复杂映射)。
    • 将这个 Authentication 写入当前线程的 SecurityContextHolder 中,代表“当前请求已认证”。
  • 校验失败时的处理
    • 若没有 Token、Token 非法或过期,JwtDecoder 会抛出异常;
    • 资源服务器模块会返回 401 或 403 响应,Controller 不会被执行。

六、Controller 层:@AuthenticationPrincipal Jwt的注入机制

回到 AuthController 中的 /me 接口:

@GetMapping("/me")
public AuthUserResponse me(@AuthenticationPrincipal Jwt jwt) {
    long userId = jwtService.extractUserId(jwt);
    return authService.me(userId);
}

这里的 jwt 参数是如何被自动注入的?

  • Spring MVC 参数解析 + Spring Security 协作
    • Spring MVC 调用 Handler 方法前,会为每个参数寻找“数据来源”;
    • 当发现参数上有 @AuthenticationPrincipal 注解时,会启用专门的参数解析器:
    • SecurityContextHolder.getContext().getAuthentication() 中获取当前 Authentication
    • 默认取 authentication.getPrincipal()
  • 尝试匹配/转换为方法参数所声明的类型。
    • 在资源服务器 JWT 场景下,principal 正是 Jwt
    • 前面的认证流程中,JwtAuthenticationProvider 构造的是 JwtAuthenticationToken
    • getPrincipal() 返回的就是 org.springframework.security.oauth2.jwt.Jwt 对象;
    • 因此,当方法参数声明为 @AuthenticationPrincipal Jwt jwt 时,类型就刚好匹配,可以直接注入。
  • 业务层通过 Claim 完成“当前用户”的识别
    • 本项目在签发 Token 时把用户 ID 放在 uid Claim 中;
    • 因此在 /me 中,可以通过 jwtService.extractUserId(jwt) 从 Claim 集合中读取 uid,作为当前登录用户的唯一标识;
    • 后续调用 authService.me(userId) 查询用户信息并返回。

七、口头表达总结:如何在面试中讲清这条链路

如果在面试中被问到“你这个项目里 JWT 是如何鉴权的?@AuthenticationPrincipal Jwt 的 Jwt 从哪来的?”,可以这样组织回答:

我这边是基于 Spring Security 的 OAuth2 Resource Server,做了基于 JWT 的无状态鉴权。不用 Session 记录登录状态,靠请求头中携带的 token 进行鉴权、记录登录状态。配置层面,配置基于 RSA 公钥的 JWT 的解码器注入 ioc 容器中。在 SpringSecurity 配置类中启用资源服务器的 JWT 模式,安全过滤器链中会添加一个 Bearer Token 过滤器,所有非白名单接口都会先走一遍这个过滤器,从请求头里提取 Token ,交由解码器进行校验与解析,若没有 Token、Token 非法或过期,解码器会抛出异常,返回 401 或 403 响应,Controller 不会被执行。校验通过后会构造一个 Jwt 对象交由线程上下文进行管理。Controller 里使用 @AuthenticationPrincipal Jwt jwt 这种方式,Spring MVC 会从 SecurityContext 中拿出这个 Jwt 对象注入到参数里。到这一步鉴权完成,然后通过我封装的方法,可以从 Jwt 对象的声明中(Claim)里取出登录的用户 ID,整个过程是完全无状态的。

八、总结

整条从 Authorization: Bearer@AuthenticationPrincipal Jwt 的链路,可以概括为四层:

  • 配置层SecurityFilterChain 启用 JWT Resource Server 模式,AuthConfiguration 提供 JwtEncoder/JwtDecoder Bean。
  • 过滤器层:Bearer Token 过滤器从请求头里提取 Token,交由 JwtAuthenticationProvider 使用 JwtDecoder 校验与解析。
  • 安全上下文层:认证通过后,JwtAuthenticationToken 被写入 SecurityContext,代表当前线程的认证状态。
  • 控制器层@AuthenticationPrincipal JwtSecurityContext 中获取 principal 注入到方法参数,业务通过 Claim 完成用户识别。

掌握这条链路之后,我们不仅能在项目中更好地调试和扩展安全逻辑,也能在面试中把“Spring Security + JWT 鉴权”讲得更清晰、更体系化。

到此这篇关于一次完整的Spring Security JWT 鉴权链路解析的文章就介绍到这了,更多相关Spring Security JWT 鉴权链路内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java异常:异常处理--try-catch结构详解

    java异常:异常处理--try-catch结构详解

    今天小编就为大家分享一篇关于Java异常处理之try...catch...finally详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2021-09-09
  • SpringEvents与异步事件驱动案例详解

    SpringEvents与异步事件驱动案例详解

    本文深入探讨了SpringBoot中的事件驱动架构,特别是通过Spring事件机制实现组件解耦和系统扩展性增强,介绍了事件的发布者、事件本身、事件监听器和事件处理器的概念,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • ShardingSphere 分库分表原理与Spring Boot集成实践方案

    ShardingSphere 分库分表原理与Spring Boot集成实践方案

    本文探讨了ShardingSphere分库分表原理及其Spring Boot集成方案,详细阐述了SQL解析、分片路由、SQL改写、结果归并和事务管理等关键技术原理,感兴趣的朋友跟随小编一起看看吧
    2026-02-02
  • Java查找不重复无序数组中是否存在两个数字的和为某个值

    Java查找不重复无序数组中是否存在两个数字的和为某个值

    今天小编就为大家分享一篇关于Java查找不重复无序数组中是否存在两个数字的和为某个值,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • 浅谈Spring中几个PostProcessor的区别与联系

    浅谈Spring中几个PostProcessor的区别与联系

    这篇文章主要介绍了浅谈Spring中几个PostProcessor的区别与联系,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • java实现打砖块游戏算法

    java实现打砖块游戏算法

    这篇文章主要为大家详细介绍了java实现打砖块游戏算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java Scanner 类最佳实践

    Java Scanner 类最佳实践

    本文将深入解析Scanner类的核心功能、使用场景及最佳实践,帮助开发者高效处理控制台输入、文件读取等场景,感兴趣的朋友一起看看吧
    2025-07-07
  • Java中的RPC框架Dubbo原理和机制详解

    Java中的RPC框架Dubbo原理和机制详解

    这篇文章主要介绍了Java中的RPC框架Dubbo原理和机制详解,Dubbo 是一款Java RPC框架,致力于提供高性能的 RPC 远程服务调用方案,作为主流的微服务框架之一,Dubbo 为开发人员带来了非常多的便利,需要的朋友可以参考下
    2024-01-01
  • Hibernate组件映射代码详解

    Hibernate组件映射代码详解

    这篇文章主要介绍了Hibernate组件映射代码详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • java基于NIO实现群聊模式

    java基于NIO实现群聊模式

    这篇文章主要为大家详细介绍了java实现NIO实现群聊模式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11

最新评论