java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security JWT 鉴权链路

Spring Security JWT 鉴权链路完整解析

作者:知识即是力量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();
}

这里有几个关键点:

这样一来,我们就不需要自己在每个接口里手动解析请求头,整个 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();
    }
}

关键点
一旦容器中存在一个 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");
    }
}

在这里:

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

当一个请求携带:

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

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

回到 AuthController 中的 /me 接口:

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

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

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

如果在面试中被问到“你这个项目里 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 的链路,可以概括为四层:

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

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

您可能感兴趣的文章:
阅读全文