為什麽需要無感重新整理Token?
最近瀏覽到一個文章裏面的提問,是這樣的:當我在系統頁面上做業務操作的時候會出現突然閃退的情況,然後跳轉到登入頁面需要重新登入系統,系統使用了Redis做緩存來儲存使用者ID,和使用者的token資訊,這是什麽問題呢?
解答:
突然閃退,一般都是由於你的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的過期時間延長。
前端采用的是雙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.和後端的解決方案一樣,前端做一個類似草稿箱的功能對表單等元素進行保存
來源:juejin.cn/post/7316797749517631515
END
看完本文有收獲?請轉發分享給更多人
關註「Java編程鴨」,提升Java技能
關註Java編程鴨微信公眾號,後台回復:碼農大禮包可以獲取最新整理的技術資料一份。涵蓋Java 框架學習、架構師學習等!
文章有幫助的話,在看,轉發吧。
謝謝支持喲 (*^__^*)