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

SpringBoot實(shí)現(xiàn)JWT動(dòng)態(tài)密鑰輪換的示例詳解

 更新時(shí)間:2025年10月15日 08:22:05   作者:風(fēng)象南  
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)JWT動(dòng)態(tài)密鑰輪換的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

背景:為什么 JWT 密鑰也要"輪換"

JWT(JSON Web Token) 是當(dāng)代認(rèn)證體系的常用方案, 無(wú)論是單體系統(tǒng)、微服務(wù)、還是前后端分離登錄,幾乎都會(huì)用到它。

但在大多數(shù)系統(tǒng)里,簽名密鑰往往是一成不變的—— 一旦生成,常年不換,代碼里寫死或放在配置文件中。

這其實(shí)非常危險(xiǎn):

  • 一旦密鑰被誤傳或泄露,攻擊者就能偽造任意用戶的合法 Token
  • 無(wú)論是測(cè)試環(huán)境誤配置,還是日志誤打出 key,都可能導(dǎo)致密鑰泄露,帶來(lái)安全隱患

于是我們面臨一個(gè)工程問(wèn)題:

"如何能動(dòng)態(tài)更新 JWT 簽名密鑰,且不讓用戶重新登錄?"

目標(biāo):密鑰可定期更新,但不影響登錄狀態(tài)

我們的目標(biāo)是實(shí)現(xiàn):

時(shí)間點(diǎn)動(dòng)作用戶狀態(tài)
10月1日使用 keypair_A 生成 JWT正常
10月10日上線 keypair_B,新簽發(fā)用它老 Token 仍有效
10月20日老 Token 全部過(guò)期刪除 keypair_A
  • 老 Token 正??沈?yàn)簽
  • 新 Token 自動(dòng)使用新密鑰
  • 用戶無(wú)感知,不掉線

簽名實(shí)現(xiàn):HMAC vs RSA

JWT 支持多種簽名算法,常見(jiàn)的有兩種:

類型算法示例是否對(duì)稱特點(diǎn)
HMAC(對(duì)稱)HS256 / HS512? 是簽發(fā)方與驗(yàn)證方共用同一密鑰
RSA / ECDSA(非對(duì)稱)RS256 / ES256? 否簽發(fā)方用私鑰簽名,驗(yàn)證方用公鑰驗(yàn)簽

很多系統(tǒng)為了圖省事,默認(rèn)使用 HMAC(例如 HS256)。 它確實(shí)簡(jiǎn)單,但存在一個(gè)致命問(wèn)題:

一旦 HMAC 密鑰泄露,攻擊者可以偽造任何合法 Token。

這意味著:

簽發(fā)方 = 驗(yàn)證方 = 攻擊方(如果密鑰泄露)

沒(méi)有信任隔離

無(wú)法安全輪換:新舊密鑰都得讓驗(yàn)證邏輯同時(shí)持有

這也是為什么更高安全等級(jí)的系統(tǒng)都改用 RSA / ECDSA 非對(duì)稱簽名。

安全輪換的關(guān)鍵:KID(Key ID)+ 多版本密鑰倉(cāng)庫(kù)

JWT Header 允許帶一個(gè) "kid" 字段,用來(lái)標(biāo)識(shí)當(dāng)前簽名使用的密鑰版本。 比如:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-20251013-956"
}

這樣,驗(yàn)證方只需要:

  • 讀取 header.kid
  • 去 KeyStore 找對(duì)應(yīng)公鑰
  • 使用它來(lái)驗(yàn)簽

老 Token 用老公鑰,新 Token 用新公鑰,完美共存。

核心實(shí)現(xiàn)

技術(shù)架構(gòu)

后端技術(shù)棧:

  • Spring Boot 3 + Spring Scheduling
  • JJWT 0.12.3(JWT 處理庫(kù))
  • RSA 2048 非對(duì)稱加密
  • 內(nèi)存 ConcurrentHashMap 存儲(chǔ)(方便快速體驗(yàn)DEMO)

前端技術(shù)棧:

  • HTML5 + CSS3 + JavaScript ES6
  • Tailwind CSS UI 框架
  • 前后端分離

核心組件設(shè)計(jì)

1.DynamicKeyStore - 動(dòng)態(tài)密鑰存儲(chǔ)管理器

@Service
public class DynamicKeyStore {
    // 線程安全的密鑰存儲(chǔ)
    private final Map<String, KeyInfo> keyStore = new ConcurrentHashMap<>();
    private volatile String currentKeyId;

    // 生成新密鑰對(duì)
    public String generateNewKeyPair() {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048, new SecureRandom());
        KeyPair keyPair = generator.generateKeyPair();

        String keyId = "key-" + LocalDate.now() + "-" + timestamp;
        KeyInfo keyInfo = new KeyInfo(keyId, keyPair);

        // 輪換邏輯:舊密鑰標(biāo)記為非活躍,新密鑰設(shè)為當(dāng)前
        if (currentKeyId != null) {
            keyStore.get(currentKeyId).setActive(false);
        }
        currentKeyId = keyId;
        keyStore.put(keyId, keyInfo);

        return keyId;
    }

    // 根據(jù)KID獲取密鑰(支持多版本共存)
    public KeyInfo getKey(String keyId) {
        return keyStore.get(keyId);
    }
}

2.JwtTokenService - JWT 服務(wù)層

Token 生成(使用當(dāng)前活躍密鑰):

public String generateToken(String username, Map<String, Object> claims) {
    // 獲取當(dāng)前活躍密鑰
    var currentKey = keyStore.getCurrentKey();
    String keyId = currentKey.getKeyId();

    // 構(gòu)建JWT,設(shè)置KID
    JwtBuilder builder = Jwts.builder()
            .subject(username)
            .issuedAt(new Date())
            .expiration(Date.from(Instant.now().plus(24, ChronoUnit.HOURS)))
            .header().keyId(keyId).and()
            .signWith(currentKey.getKeyPair().getPrivate(), Jwts.SIG.RS256);

    // 添加自定義聲明
    if (claims != null && !claims.isEmpty()) {
        builder.claims().add(claims);
    }

    return builder.compact();
}

Token 驗(yàn)證(支持多版本密鑰):

public Claims validateToken(String token) throws JwtException {
    // 1. 解析Header獲取KID
    String[] parts = token.split("\\.");
    String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]));
    Map<String, Object> headerMap = mapper.readValue(headerJson, Map.class);
    String keyId = (String) headerMap.get("kid");

    if (keyId == null) {
        throw new JwtException("Token缺少密鑰ID (kid)");
    }

    // 2. 根據(jù)KID獲取對(duì)應(yīng)公鑰
    var keyInfo = keyStore.getKey(keyId);
    if (keyInfo == null) {
        throw new JwtException("找不到對(duì)應(yīng)的密鑰: " + keyId);
    }
    PublicKey publicKey = keyInfo.getKeyPair().getPublic();

    // 3. 使用公鑰驗(yàn)證Token
    Jws<Claims> jws = Jwts.parser()
            .verifyWith(publicKey)
            .build()
            .parseSignedClaims(token);

    return jws.getPayload();
}

3.KeyRotationScheduler - 定時(shí)輪換調(diào)度器

@Component
public class KeyRotationScheduler {

    @Value("${jwt.rotation-period-days:7}")
    private int rotationPeriodDays;

    @Value("${jwt.grace-period-days:14}")
    private int gracePeriodDays;

    // 應(yīng)用啟動(dòng)時(shí)初始化
    @EventListener(ApplicationReadyEvent.class)
    public void initialize() {
        keyStore.initialize();
    }

    // 定時(shí)輪換:每天凌晨2點(diǎn)檢查
    @Scheduled(cron = "0 0 2 * * ?")
    public void scheduledKeyRotation() {
        var currentKey = keyStore.getCurrentKey();
        long daysSinceCreation = ChronoUnit.DAYS.between(
            currentKey.getCreatedAt(), LocalDateTime.now()
        );

        if (daysSinceCreation >= rotationPeriodDays) {
            String newKeyId = keyStore.generateNewKeyPair();
            logger.info("密鑰輪換完成: {} -> {}", currentKeyId, newKeyId);
        }
    }

    // 定時(shí)清理:每天凌晨3點(diǎn)清理過(guò)期密鑰
    @Scheduled(cron = "0 0 3 * * ?")
    public void scheduledKeyCleanup() {
        List<String> removedKeys = keyStore.cleanupExpiredKeys(gracePeriodDays);
        if (!removedKeys.isEmpty()) {
            logger.info("清理了 {} 個(gè)過(guò)期密鑰", removedKeys.size());
        }
    }
}

4.API接口

認(rèn)證相關(guān):

  • POST /api/auth/login - 用戶登錄
  • POST /api/auth/validate - Token驗(yàn)證
  • POST /api/auth/refresh - Token刷新
  • GET /api/auth/me - 獲取當(dāng)前用戶信息

管理功能:

  • POST /api/auth/admin/rotate-keys - 手動(dòng)輪換密鑰
  • POST /api/auth/admin/cleanup-keys - 清理過(guò)期密鑰

演示功能:

  • GET /api/demo/key-stats - 獲取密鑰統(tǒng)計(jì)
  • POST /api/demo/parse-token - 解析Token
  • POST /api/demo/generate-test-token - 生成測(cè)試Token
  • GET /api/demo/protected - 受保護(hù)資源

5.前端交互界面

DEMO提供了完整的前后端分離演示界面

用戶登錄:登錄認(rèn)證和狀態(tài)顯示

受保護(hù)資源:演示Token保護(hù)機(jī)制

密鑰信息:實(shí)時(shí)密鑰存儲(chǔ)狀態(tài)監(jiān)控

Token解析:JWT結(jié)構(gòu)分析工具

管理功能:手動(dòng)密鑰輪換和清理

平滑過(guò)渡策略

密鑰輪換不是"替換",而是"共存"。

階段動(dòng)作狀態(tài)
① 新密鑰上線新 Token 用新 Key 簽發(fā)雙密鑰并行
② 老 Token 仍驗(yàn)證通過(guò)舊 Key 在驗(yàn)證端保留用戶無(wú)感
③ 老 Token 過(guò)期刪除舊 Key安全收尾

整個(gè)過(guò)程無(wú)須人工干預(yù),也不需要讓用戶重新登錄。

關(guān)鍵驗(yàn)證點(diǎn)

  • 新Token使用新密鑰:輪換后新生成的Token包含新的KID
  • 舊Token仍可驗(yàn)證:輪換前的Token繼續(xù)正常使用
  • 用戶無(wú)感知:整個(gè)輪換過(guò)程對(duì)用戶完全透明
  • 系統(tǒng)監(jiān)控:實(shí)時(shí)查看密鑰狀態(tài)和輪換歷史

總結(jié)

在實(shí)際項(xiàng)目中,密鑰管理往往是被忽視的角落。直到安全審計(jì)時(shí)才發(fā)現(xiàn)問(wèn)題。通過(guò)合理運(yùn)用JWT的KID字段和RSA的非對(duì)稱特性,我們可以讓系統(tǒng)自動(dòng)處理密鑰輪換,而不是事后補(bǔ)救。

從代碼量來(lái)看,增加密鑰輪換功能并不需要大幅改動(dòng)現(xiàn)有架構(gòu),但帶來(lái)的安全收益是長(zhǎng)期的。

到此這篇關(guān)于SpringBoot實(shí)現(xiàn)JWT動(dòng)態(tài)密鑰輪換的示例詳解的文章就介紹到這了,更多相關(guān)SpringBoot JWT動(dòng)態(tài)密鑰輪換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)優(yōu)先隊(duì)列式廣度優(yōu)先搜索算法的示例代碼

    Java實(shí)現(xiàn)優(yōu)先隊(duì)列式廣度優(yōu)先搜索算法的示例代碼

    這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)優(yōu)先隊(duì)列式廣度優(yōu)先搜索算法,文中通過(guò)一個(gè)示例帶大家具體了解了實(shí)現(xiàn)的方法,需要的可以參考一下
    2022-08-08
  • 淺談spring boot使用thymeleaf版本的問(wèn)題

    淺談spring boot使用thymeleaf版本的問(wèn)題

    這篇文章主要介紹了spring boot使用thymeleaf版本的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 解決SpringBoot多模塊發(fā)布時(shí)99%的問(wèn)題

    解決SpringBoot多模塊發(fā)布時(shí)99%的問(wèn)題

    本文歸納了以下 8 個(gè)原則和發(fā)布時(shí)經(jīng)常出現(xiàn)的 4 個(gè)問(wèn)題的解決方案,掌握了這些原則和解決方案,幾乎可以解決絕大數(shù)SpringBoot發(fā)布問(wèn)題
    2019-07-07
  • Maven本地打包war包實(shí)現(xiàn)代碼解析

    Maven本地打包war包實(shí)現(xiàn)代碼解析

    這篇文章主要介紹了Maven本地打包war包實(shí)現(xiàn)代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • 詳細(xì)聊聊RabbitMQ竟無(wú)法反序列化List問(wèn)題

    詳細(xì)聊聊RabbitMQ竟無(wú)法反序列化List問(wèn)題

    這篇文章主要給大家介紹了關(guān)于RabbitMQ竟無(wú)法反序列化List的相關(guān)資料,文中通過(guò)示例代碼將問(wèn)題以及解決的過(guò)程介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Spring MVC 圖片的上傳和下載功能

    Spring MVC 圖片的上傳和下載功能

    SSM 框架是一種基于Java的Web開(kāi)發(fā)框架,其中Spring作為控制層、SpringMVC作為視圖層、MyBatis作為持久層,這個(gè)框架非常適合Web應(yīng)用程序的開(kāi)發(fā),這篇文章主要介紹了Spring MVC 圖片的上傳和下載功能,需要的朋友可以參考下
    2023-03-03
  • java swing實(shí)現(xiàn)電影購(gòu)票系統(tǒng)

    java swing實(shí)現(xiàn)電影購(gòu)票系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java swing實(shí)現(xiàn)電影購(gòu)票系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • Springmvc ajax跨域請(qǐng)求處理方法實(shí)例詳解

    Springmvc ajax跨域請(qǐng)求處理方法實(shí)例詳解

    這篇文章主要介紹了Springmvc ajax跨域請(qǐng)求處理方法實(shí)例詳解,需要的朋友可以參考下
    2017-10-10
  • Java+MySQL實(shí)現(xiàn)設(shè)計(jì)優(yōu)惠券系統(tǒng)

    Java+MySQL實(shí)現(xiàn)設(shè)計(jì)優(yōu)惠券系統(tǒng)

    這篇文章主要介紹了Java+MySQL實(shí)現(xiàn)設(shè)計(jì)優(yōu)惠券系統(tǒng),文章基于Java與MySQL的相關(guān)資料展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • 關(guān)于Lists.partition集合分組使用以及注意事項(xiàng)

    關(guān)于Lists.partition集合分組使用以及注意事項(xiàng)

    這篇文章主要介紹了關(guān)于Lists.partition集合分組使用以及注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01

最新評(píng)論

绥芬河市| 盘山县| 兴隆县| 云和县| 南安市| 三明市| 东辽县| 成安县| 汽车| 山丹县| 巴塘县| 封开县| 马关县| 凭祥市| 兴宁市| 万荣县| 安吉县| 巴里| 甘德县| 定边县| 凤冈县| 广丰县| 兴和县| 阳东县| 承德县| 竹溪县| 密山市| 县级市| 安阳市| 会泽县| 高阳县| 嵊泗县| 名山县| 手游| 闸北区| 谷城县| 南江县| 西宁市| 兴隆县| 弋阳县| 南阳市|