目录
在前后端分离架构中,Token 是实现身份认证的核心机制,但 Token 过期会导致用户操作中断,影响体验。因此,自动续期方案是保障用户持续访问的关键。以下是几种主流的 Token 自动续期方案,包含原理、实现细节及安全性考量。
一、刷新 Token(Refresh Token)机制
这是目前最成熟、应用最广泛的方案,核心是通过“双 Token 体系”实现续期,兼顾安全性和用户体验。
1. 核心原理
- Access Token(访问令牌):短期有效(如 15 分钟),用于接口访问,权限范围小,泄露风险低。
- Refresh Token(刷新令牌):长期有效(如 7 天),仅用于获取新的 Access Token,权限范围仅为“续期”,泄露影响可控。
当 Access Token 过期时,前端用未过期的 Refresh Token 向后端换取新的 Access Token,避免用户重新登录。
2. 前后端实现流程
前端实现(以 Axios 为例)
-
存储 Token:Access Token 存在内存或 httpOnly Cookie(推荐,防 XSS);Refresh Token 存在 httpOnly Cookie(避免前端 JS 读取,增强安全性)。
-
请求拦截器:每次请求在 Header 中携带 Access Token(如
Authorization: Bearer {accessToken})。 -
响应拦截器:处理 401 错误(Access Token 过期):
- 若存在 Refresh Token,调用后端“刷新接口”获取新 Access Token。
- 刷新成功后,更新本地 Access Token,并重试原请求。
- 若 Refresh Token 也过期(刷新接口返回 401),则清除本地 Token,跳转至登录页。
// 响应拦截器示例 axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 若为401且未尝试过刷新 if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; // 标记已尝试刷新,避免重复请求 try { // 调用刷新接口(Refresh Token 通过 Cookie 自动携带) const { data } = await axios.post('/api/refresh-token'); // 更新本地 Access Token localStorage.setItem('accessToken', data.accessToken); // 重试原请求(携带新 Token) originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`; return axios(originalRequest); } catch (refreshError) { // 刷新失败(Refresh Token 过期),跳转登录 window.location.href = '/login'; return Promise.reject(refreshError); } } return Promise.reject(error); } ); -
并发请求处理:若多个请求同时触发 401,需避免重复刷新 Token。可通过“锁机制”实现:刷新过程中阻塞其他请求,等待新 Token 生成后统一重试。
后端实现
-
生成 Token:登录成功时,同时生成 Access Token(短期)和 Refresh Token(长期),返回给前端(Access Token 可放响应体,Refresh Token 放 httpOnly Cookie)。
-
验证 Access Token:接口请求时,验证 Access Token 的签名和有效期,无效则返回 401。
-
刷新接口实现:
- 接收 Refresh Token(从 Cookie 或请求体获取),验证其有效性(是否存在于数据库、未过期、未被吊销)。
- 验证通过后,生成新的 Access Token(和新的 Refresh Token,可选,增强安全性)。
- 返回新 Token 给前端。
// 后端刷新接口示例(伪代码) @PostMapping("/api/refresh-token") public Result refreshToken(HttpServletRequest request) { // 从 Cookie 获取 Refresh Token String refreshToken = CookieUtils.getCookieValue(request, "refreshToken"); if (refreshToken == null) { return Result.fail(401, "刷新令牌不存在"); } // 验证 Refresh Token(查库+验签) RefreshTokenEntity entity = refreshTokenMapper.selectByToken(refreshToken); if (entity == null || entity.getExpireTime().before(new Date())) { return Result.fail(401, "刷新令牌已过期"); } // 生成新 Access Token 和 Refresh Token String newAccessToken = JwtUtils.generateAccessToken(entity.getUserId()); String newRefreshToken = JwtUtils.generateRefreshToken(entity.getUserId()); // 更新数据库中的 Refresh Token(旧的标记失效,存储新的) refreshTokenMapper.invalidateOldToken(entity.getId()); refreshTokenMapper.insert(new RefreshTokenEntity(newRefreshToken, entity.getUserId())); // 返回新 Token(Access Token 放响应体,Refresh Token 放 httpOnly Cookie) return Result.success(Map.of( "accessToken", newAccessToken, "refreshToken", newRefreshToken // 可选,通过 Cookie 返回更安全 )); }
3. 优缺点
- 优点:安全性高(Access Token 短期有效,泄露影响小;Refresh Token 可吊销);用户体验好(无需频繁登录)。
- 缺点:实现较复杂(需维护双 Token 逻辑);需后端存储 Refresh Token(增加存储成本)。
二、滑动窗口续期(无 Refresh Token)
适用于对安全性要求中等、希望简化实现的场景,核心是“Token 快过期时自动延长有效期”。
1. 核心原理
- 仅使用一个 Access Token,有效期设为中等(如 1 小时)。
- 当 Token 剩余有效期小于阈值(如 10 分钟)时,前端请求接口时,后端自动生成新 Token 并返回,前端更新本地 Token。
- 本质是“用持续活跃的请求延长 Token 有效期”,类似“用户活跃则会话不失效”。
2. 前后端实现流程
前端实现
-
存储 Token 并在请求头携带。
-
每次收到响应后,检查是否包含新 Token(如响应头
X-New-Token),若有则更新本地存储。// 响应拦截器示例 axios.interceptors.response.use( (response) => { // 若响应头有新 Token,更新本地存储 const newToken = response.headers['x-new-token']; if (newToken) { localStorage.setItem('accessToken', newToken); // 建议用 Cookie } return response; }, (error) => { // 若 Token 已过期(401),直接跳转登录 if (error.response?.status === 401) { window.location.href = '/login'; } return Promise.reject(error); } );
后端实现
-
Token 中包含过期时间(如
exp字段)。 -
每次验证 Token 时,计算剩余有效期:若剩余时间 < 阈值(如 10 分钟),则生成新 Token,放在响应头(如
X-New-Token)返回。// 验证 Token 并判断是否续期(伪代码) public String validateAndRenewToken(String token) { // 验证 Token 签名和有效性 Claims claims = JwtUtils.parseToken(token); if (claims == null) { throw new TokenInvalidException(); } // 计算剩余有效期(当前时间到 exp 的差值) long expireTime = claims.getExpiration().getTime(); long remainingTime = expireTime - System.currentTimeMillis(); // 若剩余时间 < 10 分钟,生成新 Token if (remainingTime < 10 * 60 * 1000) { String newToken = JwtUtils.generateAccessToken(claims.getSubject()); // 用原用户 ID 生成新 Token return newToken; } return null; // 无需续期 }
3. 优缺点
- 优点:实现简单(无需维护 Refresh Token);用户体验流畅(无感续期)。
- 缺点:安全性较低(Token 有效期较长,泄露后风险更高);无法处理“用户长时间不操作”的场景(Token 会直接过期)。
三、主动预检续期
适用于对请求成功率要求高的场景,前端主动检测 Token 有效期,提前续期。
1. 核心原理
- 前端每次发送请求前,检查 Token 剩余有效期:
- 若剩余时间 > 阈值(如 5 分钟):直接发送请求。
- 若剩余时间 ≤ 阈值:先发送续期请求获取新 Token,再发送原请求。
- 避免了“请求因 Token 过期失败后重试”的情况,更主动。
2. 前后端实现流程
前端实现(请求拦截器中处理)
axios.interceptors.request.use(async (config) => {
const token = localStorage.getItem('accessToken');
if (!token) {
window.location.href = '/login';
return Promise.reject(new Error('未登录'));
}
// 解析 Token 中的过期时间(需前端解析 JWT,或后端返回过期时间)
const payload = JSON.parse(atob(token.split('.')[1]));
const expireTime = payload.exp * 1000; // JWT 的 exp 是秒级时间戳
const remainingTime = expireTime - Date.now();
// 若剩余时间 ≤ 5 分钟,先续期
if (remainingTime <= 5 * 60 * 1000) {
try {
const { data } = await axios.post('/api/renew-token', { token });
const newToken = data.newToken;
localStorage.setItem('accessToken', newToken);
config.headers['Authorization'] = `Bearer ${newToken}`;
} catch (error) {
// 续期失败,跳转登录
window.location.href = '/login';
return Promise.reject(error);
}
} else {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
后端实现
- 提供专门的续期接口(
/api/renew-token),接收旧 Token,验证有效性后返回新 Token。 - 逻辑与滑动窗口的后端类似,但触发时机由前端主动控制。
3. 优缺点
- 优点:避免请求失败重试,成功率高;主动处理续期,流程更可控。
- 缺点:增加了一次预检请求(性能损耗小);需前端解析 Token 过期时间(JWT 可解析,非 JWT 需后端额外返回)。
四、安全性与最佳实践
-
Token 存储:
- 优先用
httpOnly + Secure + SameSiteCookie 存储(防 XSS、CSRF 攻击)。 - 避免用
localStorage/sessionStorage(易被 XSS 窃取)。
- 优先用
-
过期时间设置:
- Access Token:短期(15 分钟 ~ 1 小时),减少泄露影响。
- Refresh Token:中期(7 ~ 30 天),兼顾体验与安全;可设置“绝对过期时间”(如 30 天),强制用户定期登录。
-
Refresh Token 轮换:
- 每次用 Refresh Token 换取新 Access Token 时,同步生成新的 Refresh Token(旧的立即失效),防止 Refresh Token 被复用。
-
HTTPS 传输:所有 Token 必须通过 HTTPS 传输,防止中间人攻击。
-
并发请求处理:
- 用“锁机制”(如 Promise 等待)处理多个请求同时触发续期的场景,避免重复调用刷新接口。
-
Token 吊销:
- 后端需支持主动吊销 Token(如用户登出、修改密码时),可通过“黑名单”(Redis 存储已吊销 Token)实现快速验证。
总结
- 追求安全性:优先选 Refresh Token 机制(双 Token 体系)。
- 追求简单实现:选 滑动窗口续期(无 Refresh Token)。
- 追求请求成功率:选 主动预检续期(前端提前处理)。
实际场景中,可结合业务需求(如用户活跃度、安全等级)选择方案,核心是在“用户体验”与“安全性”之间找到平衡。

1207

被折叠的 条评论
为什么被折叠?



