最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Security JWT 鑒權鏈路完整解析

 更新時間:2026年02月03日 09:08:19   作者:知識即是力量ol  
文章詳細解析了SpringSecurity結合JWT實現(xiàn)鑒權的完整鏈路,從配置到代碼,涵蓋了從請求到業(yè)務層的每一個步驟,包括配置資源服務器模式、JWT編解碼、自定義JWT服務、請求過濾和Controller層的注入機制

一次完整的 Spring Security JWT 鑒權鏈路解析

在實際項目中,我們經(jīng)常會在 Controller 里寫出這樣一個方法簽名:

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

看起來 Spring 能“憑空”把一個 Jwt 對象塞進方法參數(shù)里,還自動從請求頭里的 Authorization: Bearer 提取 Token 并完成校驗。

這篇文章就結合項目,從配置代碼,串起這條鑒權鏈路的每一步。

一、整體流程總覽

從前端調(diào)用 /api/v1/auth/me 開始,到方法參數(shù)里拿到 Jwt jwt,整個鏈路可以概括為:

  1. 前端發(fā)請求:HTTP 請求頭里帶 Authorization: Bearer <accessToken>
  2. Security 過濾器攔截SecurityFilterChain 中的 Bearer Token 過濾器從請求頭里截出 <accessToken>。
  3. 交給 JwtDecoder 校驗解析:過濾器調(diào)用配置好的 JwtDecoder(基于 RSA 公鑰的 NimbusJwtDecoder),做簽名、過期等校驗,解析出 Claim,得到一個 Jwt 對象。
  4. 封裝 Authentication 放入 SecurityContext:框架把 Jwt 封裝成 JwtAuthenticationToken,寫入當前線程的 SecurityContext。
  5. Controller 使用 @AuthenticationPrincipal Jwt 注入當前用戶 Token:@AuthenticationPrincipalSecurityContext 里取出 principal(類型為 Jwt)注入到方法參數(shù)。
  6. 業(yè)務層解析用戶 IDjwtService.extractUserId(jwt) 從 Claim 中讀取 uid,實現(xiàn)當前登錄用戶識別與后續(xù)業(yè)務處理。

下面按照“配置層 → 過濾器層 → 控制器層”的順序,一步步展開。

二、開啟資源服務器模式: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()
                    // 公開內(nèi)容:首頁 Feed 不需要登錄
                    .requestMatchers("/api/v1/knowposts/feed").permitAll()
                    // 知文詳情等其他白名單接口...
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()));
    return http.build();
}

這里有幾個關鍵點:

  • 無狀態(tài)會話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 統(tǒng)一接管。

三、JWT 編解碼 Bean:JwtEncoder與JwtDecoder

要讓資源服務器真正“識別”JWT,我們必須告訴它如何簽發(fā)和校驗 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;
    • 用于登錄/注冊成功后簽發(fā) Access Token 與 Refresh Token。
  • JwtDecoder
    • 只讀取 RSA 公鑰;
    • 創(chuàng)建 NimbusJwtDecoder,用于校驗簽名、過期時間等信息并解析 Claim。

關鍵點
一旦容器中存在一個 JwtDecoder Bean,oauth2ResourceServer().jwt() 會自動使用它來完成 JWT 的解析與驗證,無需手動再把 JwtDecoder 綁定到過濾器上。

四、自定義 JWT 服務:簽發(fā)與解析的業(yè)務封裝JwtService

在業(yè)務層,我們通過 JwtService 把 Token 的簽發(fā)與解析從 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");
    }
}

在這里:

  • 簽發(fā)階段
    • 使用 JwtEncoder 構造帶有 uidtoken_type、jti 等 Claim 的 Token;
    • Access Token 與 Refresh Token 使用不同的 TTL 與類型標記。
  • 解析階段
    • 可以通過 decode() 手動解析某個 Token(例如刷新或登出時用);
    • 對于 /me 等需要“當前用戶”的接口,更多使用 extractUserId(jwt),從已經(jīng)被框架解析過的 Jwt 中讀取 uid Claim。

五、請求進入時:Filter 如何一步步完成 JWT 鑒權

當一個請求攜帶:

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

到達服務端時,Spring Security 會按如下步驟進行處理:

  • 進入 SecurityFilterChain
    • 請求首先會進入配置好的安全過濾器鏈;
    • 因為啟用了 .oauth2ResourceServer().jwt(),鏈中包含 Bearer Token 相關過濾器。
    • 從請求頭中提取 Bearer Token
    • 過濾器內(nèi)部使用 BearerTokenResolver(默認實現(xiàn)為 DefaultBearerTokenResolver):
    • 從請求頭里讀取 Authorization;
  • 匹配 Bearer 前綴;
    • 截取出 <accessToken> 部分。
    • 創(chuàng)建未認證的 Authentication 并委托給 AuthenticationManager
    • 根據(jù) Token 創(chuàng)建一個 BearerTokenAuthenticationToken,此時它還處于“未認證”狀態(tài);
    • 將該對象交由 AuthenticationManager(內(nèi)部委托給 JwtAuthenticationProvider)處理。
    • JwtAuthenticationProvider 使用 JwtDecoder 進行校驗與解析
    • JwtAuthenticationProvider 注入的就是我們前面定義的 JwtDecoder Bean;
  • 調(diào)用 jwtDecoder.decode(accessToken)
    • 使用 RSA 公鑰校驗簽名;
    • 校驗 Token 是否過期、是否生效(exp/nbf 等);
    • 解析出完整的 Claim 集合并構造 Jwt 對象。
    • 構造認證后的 JwtAuthenticationToken,寫入 SecurityContext
    • 若校驗通過,JwtAuthenticationProvider 會創(chuàng)建一個認證成功的 JwtAuthenticationToken
  • principal:解析得到的 Jwt;
    • authorities:可根據(jù) Claim 映射出的權限列表(本項目中暫未做復雜映射)。
    • 將這個 Authentication 寫入當前線程的 SecurityContextHolder 中,代表“當前請求已認證”。
  • 校驗失敗時的處理
    • 若沒有 Token、Token 非法或過期,JwtDecoder 會拋出異常;
    • 資源服務器模塊會返回 401 或 403 響應,Controller 不會被執(zhí)行。

六、Controller 層:@AuthenticationPrincipal Jwt的注入機制

回到 AuthController 中的 /me 接口:

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

這里的 jwt 參數(shù)是如何被自動注入的?

  • Spring MVC 參數(shù)解析 + Spring Security 協(xié)作
    • Spring MVC 調(diào)用 Handler 方法前,會為每個參數(shù)尋找“數(shù)據(jù)來源”;
    • 當發(fā)現(xiàn)參數(shù)上有 @AuthenticationPrincipal 注解時,會啟用專門的參數(shù)解析器:
    • SecurityContextHolder.getContext().getAuthentication() 中獲取當前 Authentication;
    • 默認取 authentication.getPrincipal()
  • 嘗試匹配/轉換為方法參數(shù)所聲明的類型。
    • 在資源服務器 JWT 場景下,principal 正是 Jwt
    • 前面的認證流程中,JwtAuthenticationProvider 構造的是 JwtAuthenticationToken
    • getPrincipal() 返回的就是 org.springframework.security.oauth2.jwt.Jwt 對象;
    • 因此,當方法參數(shù)聲明為 @AuthenticationPrincipal Jwt jwt 時,類型就剛好匹配,可以直接注入。
  • 業(yè)務層通過 Claim 完成“當前用戶”的識別
    • 本項目在簽發(fā) Token 時把用戶 ID 放在 uid Claim 中;
    • 因此在 /me 中,可以通過 jwtService.extractUserId(jwt) 從 Claim 集合中讀取 uid,作為當前登錄用戶的唯一標識;
    • 后續(xù)調(diào)用 authService.me(userId) 查詢用戶信息并返回。

七、口頭表達總結:如何在面試中講清這條鏈路

如果在面試中被問到“你這個項目里 JWT 是如何鑒權的?@AuthenticationPrincipal Jwt 的 Jwt 從哪來的?”,可以這樣組織回答:

我這邊是基于 Spring Security 的 OAuth2 Resource Server,做了基于 JWT 的無狀態(tài)鑒權。不用 Session 記錄登錄狀態(tài),靠請求頭中攜帶的 token 進行鑒權、記錄登錄狀態(tài)。配置層面,配置基于 RSA 公鑰的 JWT 的解碼器注入 ioc 容器中。在 SpringSecurity 配置類中啟用資源服務器的 JWT 模式,安全過濾器鏈中會添加一個 Bearer Token 過濾器,所有非白名單接口都會先走一遍這個過濾器,從請求頭里提取 Token ,交由解碼器進行校驗與解析,若沒有 Token、Token 非法或過期,解碼器會拋出異常,返回 401 或 403 響應,Controller 不會被執(zhí)行。校驗通過后會構造一個 Jwt 對象交由線程上下文進行管理。Controller 里使用 @AuthenticationPrincipal Jwt jwt 這種方式,Spring MVC 會從 SecurityContext 中拿出這個 Jwt 對象注入到參數(shù)里。到這一步鑒權完成,然后通過我封裝的方法,可以從 Jwt 對象的聲明中(Claim)里取出登錄的用戶 ID,整個過程是完全無狀態(tài)的。

八、總結

整條從 Authorization: Bearer@AuthenticationPrincipal Jwt 的鏈路,可以概括為四層:

  • 配置層SecurityFilterChain 啟用 JWT Resource Server 模式,AuthConfiguration 提供 JwtEncoder/JwtDecoder Bean。
  • 過濾器層:Bearer Token 過濾器從請求頭里提取 Token,交由 JwtAuthenticationProvider 使用 JwtDecoder 校驗與解析。
  • 安全上下文層:認證通過后,JwtAuthenticationToken 被寫入 SecurityContext,代表當前線程的認證狀態(tài)。
  • 控制器層@AuthenticationPrincipal JwtSecurityContext 中獲取 principal 注入到方法參數(shù),業(yè)務通過 Claim 完成用戶識別。

掌握這條鏈路之后,我們不僅能在項目中更好地調(diào)試和擴展安全邏輯,也能在面試中把“Spring Security + JWT 鑒權”講得更清晰、更體系化。

到此這篇關于一次完整的Spring Security JWT 鑒權鏈路解析的文章就介紹到這了,更多相關Spring Security JWT 鑒權鏈路內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java 中復合機制的實例詳解

    java 中復合機制的實例詳解

    這篇文章主要介紹了java 中復合機制的實例詳解的相關資料,希望通過本文大家能了解繼承和復合的區(qū)別并應用復合這種機制,需要的朋友可以參考下
    2017-09-09
  • spring 定時任務@Scheduled詳解

    spring 定時任務@Scheduled詳解

    這篇文章主要介紹了spring 定時任務@Scheduled的相關資料,文中通過示例代碼介紹的很詳細,相信對大家的理解和學習具有一定的參考借鑒價值,有需要的朋友們下面來一起看看吧。
    2017-01-01
  • 詳解JDK9特性之JPMS模塊化

    詳解JDK9特性之JPMS模塊化

    JDK9引入了一個特性叫做JPMS(Java Platform Module System),也可以叫做Project Jigsaw。模塊化的本質(zhì)就是將一個大型的項目拆分成為一個一個的模塊,每個模塊都是獨立的單元,并且不同的模塊之間可以互相引用和調(diào)用。本文將詳細介紹JDK9特性之JPMS模塊化。
    2021-06-06
  • Java中final關鍵字詳解及實例

    Java中final關鍵字詳解及實例

    這篇文章主要介紹了Java中final關鍵字詳解及實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-03-03
  • Java如何實現(xiàn)密碼加密

    Java如何實現(xiàn)密碼加密

    這篇文章主要介紹了Java如何實現(xiàn)密碼加密,幫助大家更好的理解和學習java,感興趣的朋友可以了解下
    2020-08-08
  • Java文件操作之按行讀取文件和遍歷目錄的方法

    Java文件操作之按行讀取文件和遍歷目錄的方法

    這篇文章主要介紹了Java文件操作之按行讀取文件和遞歸遍歷目錄的方法,遍歷目錄文中分別舉了遞歸和非遞歸的例子,需要的朋友可以參考下
    2016-03-03
  • java中兩個字符串的拼接、整數(shù)相加和浮點數(shù)相加實現(xiàn)代碼

    java中兩個字符串的拼接、整數(shù)相加和浮點數(shù)相加實現(xiàn)代碼

    這篇文章主要為大家介紹java中從鍵盤讀取用戶輸入兩個字符串,實現(xiàn)這兩個字符串的拼接、整數(shù)相加和浮點數(shù)相加,并輸出結果,需要的朋友可以參考下
    2021-05-05
  • 使用BigInteger實現(xiàn)除法取余

    使用BigInteger實現(xiàn)除法取余

    這篇文章主要介紹了使用BigInteger實現(xiàn)除法取余操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 詳解Spring Security的Web應用和指紋登錄實踐

    詳解Spring Security的Web應用和指紋登錄實踐

    這篇文章主要介紹了詳解Spring Security的Web應用和指紋登錄實踐,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • 詳解如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API

    詳解如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API

    在實際的應用中,我們經(jīng)常需要調(diào)用第三方API來獲取數(shù)據(jù)或執(zhí)行某些操作,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-12-12

最新評論

五寨县| 永德县| 天水市| 防城港市| 阜康市| 中牟县| 广南县| 大竹县| 恩平市| 通渭县| 梅河口市| 康乐县| 澄迈县| 西华县| 奈曼旗| 阿克苏市| 鄂伦春自治旗| 林甸县| 高淳县| 永嘉县| 光泽县| 密云县| 韶关市| 枣阳市| 邹城市| 宝应县| 扶余县| 双牌县| 林州市| 广丰县| 开远市| 永年县| 永修县| 炉霍县| 阳谷县| 特克斯县| 吉安县| 陵水| 易门县| 武城县| 会同县|