<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?

          共 11387字,需瀏覽 23分鐘

           ·

          2024-04-12 01:53

          來源:juejin.cn/post/7316797749517631515

          • 為什么需要無感刷新Token?
          • 自動刷新token
          • 前端token續(xù)約
          • 疑問及思考
          圖片

          為什么需要無感刷新Token?

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

            當我在系統(tǒng)頁面上做業(yè)務操作的時候會出現(xiàn)突然閃退的情況,然后跳轉(zhuǎ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è)務邏輯的跳轉(zhuǎn)什么的,就單純的往表單上面填東西,什么請求也沒發(fā)的情況下,后端是無法感知Token過期的

          「這種情況怎么解決?」

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

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

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

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

            對于后端來說就是AT過期了,而對于前端來說就是AT和RT都過期了,怎么處理?

          需要監(jiān)聽refresh token的過期時間,在接近過期的時候向后端發(fā)起請求來刷新refresh token 或者是定期刷新一下refresh token
          和后端的解決方案一樣,前端做一個類似草稿箱的功能對表單等元素進行保存

          推薦閱讀:

          被 GPT-4 Plus 賬號價格勸退了!

          世界的真實格局分析,地球人類社會底層運行原理

          不是你需要中臺,而是一名合格的架構師(附各大廠中臺建設PPT)

          企業(yè)IT技術架構規(guī)劃方案

          論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

          華為干部與人才發(fā)展手冊(附PPT)

          【中臺實踐】華為大數(shù)據(jù)中臺架構分享.pdf

          華為的數(shù)字化轉(zhuǎn)型方法論

          華為如何實施數(shù)字化轉(zhuǎn)型(附PPT)

          華為大數(shù)據(jù)解決方案(PPT)


          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  青青草成人免费观看 | 欧色一级黄色视频 | 大香蕉在线伊人 | 欧美国产精品一区婷婷五月天 | 全部孕妇毛片丰满孕妇孕交 |