接口不掉線,用戶不登出!SpringBoot 無感刷新 Token 全解析
在現(xiàn)代 Web 系統(tǒng)中,用戶體驗與安全性的平衡是后端開發(fā)的核心命題。本文將基于實際業(yè)務(wù)場景,全面剖析如何借助 Spring Boot 實現(xiàn)“用戶在線不中斷,身份自動續(xù)簽”的無感刷新 Token 機制,并結(jié)合前后端聯(lián)動,構(gòu)建完整的 Token 生命周期管理方案。
背景問題:為什么需要無感刷新?
想象這樣一個場景:
“我正在后臺管理系統(tǒng)中錄入數(shù)據(jù),頁面突然跳轉(zhuǎn)回登錄界面,之前填寫的內(nèi)容全沒了!”
這是典型的 Token 到期導(dǎo)致會話失效 的問題,尤其在使用 Redis 等緩存中間件存儲 Token 時尤為常見。
問題根源
后端通常通過 JWT 來實現(xiàn)無狀態(tài)身份驗證,但 JWT 的缺陷也很明顯:過期即失效,無法修改或撤銷。如果不設(shè)計 Token 刷新機制,用戶體驗將大打折扣。
核心策略:Token 無感續(xù)簽方案概述
方案一:后端自動續(xù)期(推薦)
在每次用戶請求時,后端檢查當前 Token 的有效時間:
- 若臨近過期(如小于5分鐘),則動態(tài)生成一個新 Token,加入響應(yīng)頭中返回;
- 前端攔截響應(yīng)頭,若發(fā)現(xiàn)新的 Token,與本地不一致則自動更新本地 Token。
方案二:前端主動續(xù)簽(補充方案)
- 前端維護一對 Token:
access_token
(短期)+refresh_token
(長期); - 每隔一段時間,前端使用
refresh_token
去調(diào)用刷新接口,獲取新的access_token
。
后端實現(xiàn)細節(jié)
依賴配置(pom.xml)
<dependencies>
<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>
</dependencies>
JWT 工具類 JwtUtil.java
代碼路徑:/src/main/java/com/icoderoad/auth/utils/JwtUtil.java
package com.icoderoad.auth.utils;
import io.jsonwebtoken.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
public class JwtUtil {
public static final long JWT_TTL = 1000L * 60 * 60 * 24; // 24小時
public static final String JWT_KEY = "qx";
public static String createJWT(String subject) {
return getJwtBuilder(subject, null, UUID.randomUUID().toString().replace("-", "")).compact();
}
public static String createJWT(String subject, Long ttlMillis) {
return getJwtBuilder(subject, ttlMillis, UUID.randomUUID().toString()).compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
long nowMillis = System.currentTimeMillis();
long expMillis = (ttlMillis != null ? nowMillis + ttlMillis : nowMillis + JWT_TTL);
SecretKey secretKey = generalKey();
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("icoderoad")
.setIssuedAt(new Date(nowMillis))
.setExpiration(new Date(expMillis))
.signWith(SignatureAlgorithm.HS256, secretKey);
}
public static Claims parseJWT(String jwt) throws Exception {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(jwt)
.getBody();
}
public static SecretKey generalKey() {
byte[] key = Base64.getDecoder().decode(JWT_KEY);
return new SecretKeySpec(key, 0, key.length, "AES");
}
public static Date getExpiration(String jwt) {
try {
return parseJWT(jwt).getExpiration();
} catch (Exception e) {
throw new RuntimeException("Token 解析失敗", e);
}
}
}
Token 攔截與續(xù)簽邏輯
攔截器路徑:/src/main/java/com/icoderoad/auth/interceptor/AuthInterceptor.java
public class AuthInterceptor implements HandlerInterceptor {
private static final long REFRESH_THRESHOLD = 1000L * 60 * 5; // 剩余5分鐘內(nèi)刷新
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
throw new RuntimeException("未登錄");
}
Claims claims = JwtUtil.parseJWT(token);
long now = System.currentTimeMillis();
long exp = claims.getExpiration().getTime();
if (exp - now < REFRESH_THRESHOLD) {
String newToken = JwtUtil.createJWT(claims.getSubject());
response.setHeader("X-Token-Refresh", newToken);
}
return true;
}
}
前端處理邏輯(以 Vue + Axios 為例)
前端攔截代碼:
axios.interceptors.response.use(response => {
const newToken = response.headers['x-token-refresh'];
if (newToken && newToken !== localStorage.getItem('access_token')) {
localStorage.setItem('access_token', newToken);
}
return response;
}, error => {
// 處理401
if (error.response.status === 401) {
// 可以保存草稿后跳轉(zhuǎn)登錄
}
return Promise.reject(error);
});
關(guān)于 AccessToken 和 RefreshToken 的機制說明
類型 | 用途 | 特點 |
| 攜帶用戶身份,頻繁使用 | 安全風險高,需短時過期 |
| 用于續(xù)簽 AccessToken | 不暴露給前端,一般保存在 Cookie 或 HttpOnly |
標準雙 Token 模式提升了安全性和用戶體驗,避免因 AccessToken 頻繁刷新帶來的資源浪費。
特別討論:表單靜默超時的處理策略
場景問題:
用戶長時間填寫表單,沒有發(fā)出任何請求,點擊提交時發(fā)現(xiàn) token 已失效,被重定向到登錄頁,數(shù)據(jù)全丟。
推薦方案:
- 提交失敗后前端本地緩存表單數(shù)據(jù);
- 登錄成功后回顯草稿,確保用戶體驗不受損;
- 或者在用戶輸入行為時定期心跳請求,觸發(fā)后端續(xù)簽。
總結(jié)
實現(xiàn)無感刷新 Token,是用戶體驗與安全性協(xié)同優(yōu)化的重要實踐。通過后端智能判斷與前端攔截配合,結(jié)合雙 Token 模式或動態(tài)續(xù)簽機制,我們可以實現(xiàn):
用戶操作不中斷 身份憑證自動續(xù)期 安全控制粒度更靈活