<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何做到無感刷新Token?

          共 12247字,需瀏覽 25分鐘

           ·

          2024-05-19 08:00

          因公眾號更改推送規(guī)則,請點“在看”并加“星標”第一時間獲取精彩技術分享

          點擊關注#互聯(lián)網(wǎng)架構師公眾號,領取架構師全套資料 都在這里

          0、2T架構師學習資料干貨分

          上一篇:2T架構師學習資料干貨分享

          大家好,我是互聯(lián)網(wǎng)架構師!

          • 為什么需要無感刷新Token?

          • 自動刷新token

          • 前端token續(xù)約

          • 疑問及思考

          圖片

          為什么需要無感刷新Token?

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

            當我在系統(tǒng)頁面上做業(yè)務操作的時候會出現(xiàn)突然閃退的情況,然后跳轉到登錄頁面需要重新登錄系統(tǒng),系統(tǒng)使用了Redis做緩存來存儲用戶ID,和用戶的token信息,這是什么問題呢?

          • 「解答:」

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

          • 「解決方案:」

            自動刷新token

            token續(xù)約

          • 「思路」

            如果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 class JwtUtil {
              // 有效期為
              public static final Long JWT_TTL = 60 * 60 * 1000 * 24;// 60 * 60 * 1000 * 24  一個小時
              // 設置秘鑰明文 --- 自己改就行
              public static final String JWT_KEY = "qx";
             // 用于生成uuid,用來標識唯一
              public static String getUUID(){
                  String uuid = UUID.randomUUID().toString().replaceAll("-""");//token用UUID來代替
                  return uuid;
              }

              /**
                  id      : 標識唯一
                  subject : 我們想要加密存儲的數(shù)據(jù)
                  ttl     : 我們想要設置的過期時間
               */

             // 生成token  jwt加密 subject token中要存放的數(shù)據(jù)(json格式)
              public static String createJWT(String subject) {
                  JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設置過期時間
                  return builder.compact();
              }

              // 生成token  jwt加密
              public static String createJWT(String subject, Long ttlMillis) {
                  JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設置過期時間
                  return builder.compact();
              }
            
              // 創(chuàng)建token jwt加密
              public static String createJWT(String id, String subject, Long ttlMillis) {
                  JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設置過期時間
                  return builder.compact();
              }

              private static 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數(shù)據(jù)
                          .setIssuer("sg")     // 簽發(fā)者
                          .setIssuedAt(now)      // 簽發(fā)時間
                          .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密算法簽名, 第二個參數(shù)為秘鑰
                          .setExpiration(expDate);
              }
              
              // 生成加密后的秘鑰 secretKey
              public static SecretKey generalKey() {
                  byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
                  SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
                  return key;
              }

              // jwt解密
              public static Claims parseJWT(String jwt) throws Exception {
                  SecretKey secretKey = generalKey();
                  return Jwts.parser()
                          .setSigningKey(secretKey)
                          .parseClaimsJws(jwt)
                          .getBody();
              }
          }
          • 寫個單元測試,測試一下
          @Test
          void test() 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);
          }
          // 解析令牌并獲取過期時間
          public static Date getTokenExpirationDate(String token) {
              try {
                  SecretKey secretKey = generalKey();
                  Claims claims = Jwts.parser()
                          .setSigningKey(secretKey)
                          .parseClaimsJws(token)
                          .getBody();
                  return claims.getExpiration();
              } catch (ExpiredJwtException | SignatureException e) {
                  throw new RuntimeException("Invalid token", e);
              }
          }

          Token有點長,就不放全部了

          圖片
          可以看到我們的 exp 過期時間的毫秒數(shù)為 1703651262000
          可以看到我們的 cur 當前時間的毫秒數(shù)為 1703564863035
          我們將兩者相減得到的值為 86398965ms,我們可以算一下一天的毫秒數(shù)是多少 1000 * 60 * 60 * 24 ms = 86400000ms
          這樣我們就能夠拿到token的過期時間tokenExpirationDate了
          我們就可以通過在校驗token的時候,如果token校驗通過了,此時我們拿到該token的過期時間,以(過期時間 - 當前時間)進行判斷
          如果說 (過期時間 - 當前時間) 小于約定的值,那么我們就重新根據(jù)token里面的信息,重新創(chuàng)建一個token,將新的token放到請求頭中
          返回給前端,前端去進行本地存儲更新token

          前端token續(xù)約

          token的續(xù)約偏向于前端的解決方案,即由前端來進行token的過期時間的判斷,首先前后端需要對接商量好一個token續(xù)約的接口,

          當前端發(fā)現(xiàn)這個token快要過期的時候,向后端發(fā)送該token,然后后端將該token的過期時間延長。
          「前端采用的是雙Token的方式,access-token 和 refresh-token即 AT 和 RT」
          「而對于純后端的方式,就是只有access-token這一個token」
          • 「那么問題來了 AT 和 RT 到底有什么區(qū)別?為什么需要RT?」

            「在前端實現(xiàn)方案來說,RT是用來在AT即將過期的時候,用RT獲取最新的token」

            我解釋一下我的觀點:

            AT的暴露機會更多,每個請求都要攜帶,所以設置的過期時間短一點,「減少劫持風險」
            RT只會暴露在auth服務中用來刷新at,設置的過期時間長一點,「增加便利性。」
            AT 和 RT 是為了網(wǎng)絡傳輸安全,網(wǎng)絡傳輸中,容易暴露 AT,因為 AT 時間短,暴露后風險系數(shù)才低
            「這種是標準的安全處理,其實已經(jīng)無需探討他的合理性,就好像 https 之于 http 一樣」

          疑問及思考

          要是前端有一個表單頁面,長時間不進行請求的發(fā)送,此時用戶填寫完表單了,再點擊提交的時候,后端返回401了,怎么辦?
          也就是說,雖然你后端可以無感刷新Token,但是你后端無感刷新Token的前提是:前端得發(fā)請求,如果用戶長時間不進行頁面的交互,
          即沒有進行任何業(yè)務邏輯的跳轉什么的,就單純的往表單上面填東西,什么請求也沒發(fā)的情況下,后端是無法感知Token過期的

          「這種情況怎么解決?」

          • 對于純后端的解決方案,我是這樣想的

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

            返回這個頁面,然后從本地存儲取出來再回顯到表單上面。
          • 對于前端的解決方案,我是這樣想的
            對于后端來說就是AT過期了,而對于前端來說就是AT和RT都過期了,怎么處理?
          1. 需要監(jiān)聽refresh token的過期時間,在接近過期的時候向后端發(fā)起請求來刷新refresh token 或者是定期刷新一下refresh token
          2. 和后端的解決方案一樣,前端做一個類似草稿箱的功能對表單等元素進行保存 

          來源:juejin.cn/post/7316797749517631515

          —  —

          如喜歡本文,請點擊右上角,把文章分享到朋友圈

          1、2T架構師學習資料干貨分享

          2、10000+TB 資源,阿里云盤,牛逼!!

          3、基本涵蓋了Spring所有核心知識點總結

            · END ·

          最后,關注公眾號互聯(lián)網(wǎng)架構師,在后臺回復:2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全

          如果這篇文章對您有所幫助,或者有所啟發(fā)的話,幫忙掃描上方二維碼關注一下,您的支持是我堅持寫作最大的動力。

          求一鍵三連點贊、轉發(fā)、在看

          瀏覽 68
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人亚洲天堂 | 竹菊影视一区二区三区四区 | 51精品人人搡人人妻 | 成人理伦电影无码AV | 国产精品无码素人福利 |