如何做到無感刷新Token?
共 12247字,需瀏覽 25分鐘
·
2024-05-19 08:00
因公眾號更改推送規(guī)則,請點“在看”并加“星標”第一時間獲取精彩技術分享
點擊關注#互聯(lián)網(wǎng)架構師公眾號,領取架構師全套資料 都在這里
上一篇: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有點長,就不放全部了
前端token續(xù)約
token的續(xù)約偏向于前端的解決方案,即由前端來進行token的過期時間的判斷,首先前后端需要對接商量好一個token續(xù)約的接口,
-
「那么問題來了 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 一樣」
疑問及思考
「這種情況怎么解決?」
-
對于純后端的解決方案,我是這樣想的
讓前端在表單填寫內容的時候做處理,如果提交返回的是401,那么前端就需要獲取表單存在本地存儲 然后跳轉登錄頁,登錄成功后
返回這個頁面,然后從本地存儲取出來再回顯到表單上面。 -
對于前端的解決方案,我是這樣想的 對于后端來說就是AT過期了,而對于前端來說就是AT和RT都過期了,怎么處理?
-
需要監(jiān)聽refresh token的過期時間,在接近過期的時候向后端發(fā)起請求來刷新refresh token 或者是定期刷新一下refresh token -
和后端的解決方案一樣,前端做一個類似草稿箱的功能對表單等元素進行保存
來源:juejin.cn/post/7316797749517631515
— 完 —
最后,關注公眾號互聯(lián)網(wǎng)架構師,在后臺回復:2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全。
如果這篇文章對您有所幫助,或者有所啟發(fā)的話,幫忙掃描上方二維碼關注一下,您的支持是我堅持寫作最大的動力。
