當前位置: 妍妍網 > 碼農

如何做到無感重新整理Token?

2024-04-25碼農

推薦關註

掃碼關註 後端架構師 」,選擇 星標 公眾號

重磅幹貨,第一時間送達!

責編:架構君 | 來源:https://juejin.cn/post/7316797749517631515

上一篇好文:

正文

大家好,我是後端架構師。

為什麽需要無感重新整理Token?

  • 「最近瀏覽到一個文章裏面的提問,是這樣的:」

    當我在系統頁面上做業務操作的時候會出現突然閃退的情況,然後跳轉到登入頁面需要重新登入系統,系統使用了Redis做緩存來儲存使用者ID,和使用者的token資訊,這是什麽問題呢?

  • 「解答:」

    突然閃退,一般都是由於你的token過期的問題,導致身份失效。

  • 「解決方案:」

    自動重新整理token

    token續約

  • 「思路」

    如果Token即將過期,你在驗證使用者許可權的同時,為使用者生成一個新的Token並返回給客戶端,客戶端需要更新本地儲存的Token,

    還可以做定時任務來重新整理Token,可以不生成新的Token,在快過期的時候,直接給Token增延長間

  • 自動重新整理token

    自動重新整理token是屬於後端的解決方案,由後端來檢查一個Token的過期時間是否快要過期了,如果快要過期了,就往請求頭中重新

    放一個token,然後前端那邊做攔截,拿到請求頭裏面的新的token,如果這個新的token和老的token不一致,直接將原生的token更換

    接下來拿程式碼舉例子

  • 先引入依賴

  • <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.1</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.33</version>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    </dependency>

  • 這是一個生成token的例子

  • import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    import java.util.Date;
    import java.util.UUID;
    public classJwtUtil{
    // 有效期為
    publicstaticfinal Long JWT_TTL = 60 * 60 * 1000 * 24;// 60 * 60 * 1000 * 24 一個小時
    // 設定秘鑰明文 --- 自己改就行
    publicstaticfinal String JWT_KEY = "qx";
    // 用於生成uuid,用來標識唯一
    publicstatic String getUUID(){
    String uuid = UUID.randomUUID().toString().replaceAll("-""");//token用UUID來代替
    return uuid;
    }
    /**
    id : 標識唯一
    subject : 我們想要加密儲存的數據
    ttl : 我們想要設定的過期時間
    */

    // 生成token jwt加密 subject token中要存放的數據(json格式)
    publicstatic String createJWT(String subject){
    JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設定過期時間
    return builder.compact();
    }
    // 生成token jwt加密
    publicstatic String createJWT(String subject, Long ttlMillis){
    JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設定過期時間
    return builder.compact();
    }
    // 建立token jwt加密
    publicstatic String createJWT(String id, String subject, Long ttlMillis){
    JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設定過期時間
    return builder.compact();
    }
    privatestatic JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid){
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    SecretKey secretKey = generalKey();
    long nowMillis = System.currentTimeMillis();
    Date now = new Date(nowMillis);
    if(ttlMillis==null){
    ttlMillis=JwtUtil.JWT_TTL;
    }
    long expMillis = nowMillis + ttlMillis;
    Date expDate = new Date(expMillis);
    return Jwts.builder()
    .setId(uuid) //唯一的ID
    .setSubject(subject) // 主題 可以是JSON數據
    .setIssuer("sg") // 簽發者
    .setIssuedAt(now) // 簽發時間
    .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密演算法簽名, 第二個參數為秘鑰
    .setExpiration(expDate);
    }
    // 生成加密後的秘鑰 secretKey
    publicstatic SecretKey generalKey(){
    byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
    SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    return key;
    }
    // jwt解密
    publicstatic Claims parseJWT(String jwt)throws Exception {
    SecretKey secretKey = generalKey();
    return Jwts.parser()
    .setSigningKey(secretKey)
    .parseClaimsJws(jwt)
    .getBody();
    }
    }







  • 寫個單元測試,測試一下

  • @Test
    voidtest()throws Exception {
    String token = JwtUtil.createJWT("1735209949551763457");
    System.out.println("Token: " + token);
    Date tokenExpirationDate = getTokenExpirationDate(token);
    System.out.println(tokenExpirationDate);
    System.out.println(tokenExpirationDate.toString());
    long exp = tokenExpirationDate.getTime();
    long cur = System.currentTimeMillis();
    System.out.println(exp);
    System.out.println(cur);
    System.out.println(exp - cur);
    }
    // 解析令牌並獲取過期時間
    publicstatic Date getTokenExpirationDate(String token){
    try {
    SecretKey secretKey = generalKey();
    Claims claims = Jwts.parser()
    .setSigningKey(secretKey)
    .parseClaimsJws(token)
    .getBody();
    return claims.getExpiration();
    catch (ExpiredJwtException | SignatureException e) {
    thrownew RuntimeException("Invalid token", e);
    }
    }

    Token有點長,就不放全部了

    可以看到我們的 exp 過期時間的毫秒數為 1703651262000

    可以看到我們的 cur 當前時間的毫秒數為 1703564863035

    我們將兩者相減得到的值為 86398965ms,我們可以算一下一天的毫秒數是多少 1000 * 60 * 60 * 24 ms = 86400000ms

    這樣我們就能夠拿到token的過期時間tokenExpirationDate了

    我們就可以透過在校驗token的時候,如果token校驗透過了,此時我們拿到該token的過期時間,以(過期時間 - 當前時間)進行判斷

    如果說 (過期時間 - 當前時間) 小於約定的值,那麽我們就重新根據token裏面的資訊,重新建立一個token,將新的token放到請求頭中

    返回給前端,前端去進行本地儲存更新token

    前端token續約

    token的續約偏向於前端的解決方案,即由前端來進行token的過期時間的判斷,首先前後端需要對接商量好一個token續約的介面,

    當前端發現這個token快要過期的時候,向後端發送該token,然後後端將該token的過期時間延長。

    「前端采用的是雙Token的方式,access-token 和 refresh-token即 AT 和 RT」

    「而對於純後端的方式,就是只有access-token這一個token」

  • 「那麽問題來了 AT 和 RT 到底有什麽區別?為什麽需要RT?」

    「在前端實作方案來說,RT是用來在AT即將過期的時候,用RT獲取最新的token」

    我解釋一下我的觀點:

    AT的暴露機會更多,每個請求都要攜帶,所以設定的過期時間短一點, 「減少劫持風險」

    RT只會暴露在auth服務中用來重新整理at,設定的過期時間長一點, 「增加便利性。」

    AT 和 RT 是為了網路傳輸安全,網路傳輸中,容易暴露 AT,因為 AT 時間短,暴露後風險系數才低

    「這種是標準的安全處理,其實已經無需探討他的合理性,就好像 https 之於 http 一樣」

  • 疑問及思考

    要是前端有一個表單頁面,長時間不進行請求的發送,此時使用者填寫完表單了,再點選送出的時候,後端返回401了,怎麽辦?

    也就是說,雖然你後端可以無感重新整理Token,但是你後端無感重新整理Token的前提是:前端得發請求,如果使用者長時間不進行頁面的互動,

    即沒有進行任何業務邏輯的跳轉什麽的,就單純的往表單上面填東西,什麽請求也沒發的情況下,後端是無法感知Token過期的

    「這種情況怎麽解決?」

  • 對於純後端的解決方案,我是這樣想的

    讓前端在表單填寫內容的時候做處理,如果送出返回的是401,那麽前端就需要獲取表單存在本地儲存 然後跳轉登入頁,登入成功後

    返回這個頁面,然後從本地儲存取出來再回顯到表單上面。

  • 對於前端的解決方案,我是這樣想的

    對於後端來說就是AT過期了,而對於前端來說就是AT和RT都過期了,怎麽處理?

    1. 需要監聽refresh token的過期時間,在接近過期的時候向後端發起請求來重新整理refresh token 或者是定期重新整理一下refresh token

    2. 和後端的解決方案一樣,前端做一個類似草稿箱的功能對表單等元素進行保存

    你還有什麽想要補充的嗎?

    最後,再次推薦下我們的AI星

    為了跟上AI時代我幹了一件事兒,我建立了一個知識星球社群:ChartGPT與副業。想帶著大家一起探索 ChatGPT和新的AI時代

    有很多小夥伴搞不定ChatGPT帳號,於是我們決定,凡是這三天之內加入ChatPGT的小夥伴,我們直接送一個正常可用的永久ChatGPT獨立帳戶。

    不光是增長速度最快,我們的星球品質也絕對經得起考驗,短短一個月時間,我們的課程團隊釋出了 8個專欄、18個副業計畫

    簡單說下這個星球能給大家提供什麽:

    1、不斷分享如何使用ChatGPT來完成各種任務,讓你更高效地使用ChatGPT,以及副業思考、變現思路、創業案例、落地案例分享。

    2、分享ChatGPT的使用方法、最新資訊、商業價值。

    3、探討未來關於ChatGPT的機遇,共同成長。

    4、幫助大家解決ChatGPT遇到的問題。

    5、 提供一整年的售後服務,一起搞副業

    星球福利:

    1、加入星球4天後,就送ChatGPT獨立帳號。

    2、邀請你加入ChatGPT會員交流群。

    3、贈送一份完整的ChatGPT手冊和66個ChatGPT副業賺錢手冊。

    其它福利還在籌劃中... 不過,我給你大家保證,加入星球後,收獲的價值會遠遠大於今天加入的門票費用 !

    本星球第一期原價 399 ,目前屬於試營運,早鳥價 149 ,每超過50人漲價10元,星球馬上要來一波大的漲價,如果你還在猶豫,可能最後就要以 更高價格加入了 。。

    早就是優勢。 建議大家盡早以便宜的價格加入!

    歡迎有需要的同學試試,如果本文對您有幫助,也請幫忙點個 贊 + 在看 啦!❤️

    在 還有更多優質計畫系統學習資源,歡迎分享給其他同學吧!

    PS:如果覺得我的分享不錯,歡迎大家隨手點贊、轉發、在看。

    最後給讀者整理了一份BAT大廠面試真題,需要的可掃碼加微信備註:「面試」獲取。

    版權申明:內容來源網路,版權歸原創者所有。除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!

    END

    最近面試BAT,整理一份面試資料【Java面試BAT通關手冊】,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。在這裏,我為大家準備了一份2021年最新最全BAT等大廠Java面試經驗總結。

    別找了,想獲取史上最全的Java大廠面試題學習資料

    掃下方二維碼回復面試就好了

    歷史好文:

    掃碼關註後端架構師」,選擇星標公眾號

    重磅幹貨,第一時間送達

    ,你在看嗎?