前后端无感 Token 刷新:原理与 Spring Boot 实战
在前后端分离架构中,基于 JWT 的身份认证方案非常普遍。为了兼顾安全性与用户体验,通常采用短生命周期的 Access Token 配合长生命周期的 Refresh Token。当 Access Token 过期时,通过无感刷新机制自动获取新令牌,避免用户频繁手动登录。
为什么要无感刷新
传统的 Token 机制往往面临两个矛盾:
- 频繁强制退出:Access Token 有效期过短(如 15 分钟),用户操作稍久即需重新登录,体验割裂。
- 安全隐患:若延长 Access Token 有效期,一旦令牌泄露,攻击窗口期将大幅增加。
引入 Refresh Token 的无感刷新机制,可以在保证安全的前提下,让前端在后台静默完成令牌轮换,实现真正的'无感'体验。
无感刷新原理
核心流程
整个流程依赖于前端的拦截器与后端的校验逻辑配合:
- 前端发起请求携带 Access Token。
- 后端返回 401 Unauthorized,表示令牌失效。
- 前端拦截器捕获该状态,暂停当前请求队列,调用刷新接口。
- 后端验证 Refresh Token 有效性,生成新的 Access Token 和 Refresh Token。
- 前端更新本地存储,并重试原始请求。
关键技术点
- 双 Token 机制:Access Token 用于鉴权,Refresh Token 用于续期。
- 并发控制:多个请求同时收到 401 时,应合并为一次刷新请求,避免重复调用。
- Redis 校验:Refresh Token 建议存入 Redis,支持主动失效或滑动过期策略。
前端实现
以 Axios 为例,我们需要配置请求和响应拦截器。这里的关键在于处理 isRefreshing 锁和订阅者队列,确保同一时间只有一个刷新请求在运行。
// auth.js
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
// Token 存取函数
function getAccessToken() {
return localStorage.getItem('access_token');
}
function getRefreshToken() {
return localStorage.getItem('refresh_token');
}
function () {
.(, accessToken);
.(, refreshToken);
}
api...( {
token = ();
(token) {
config.[] = ;
}
config;
});
isRefreshing = ;
subscribers = [];
() {
subscribers.( (newToken));
subscribers = [];
}
() {
subscribers.(cb);
}
api...(
res,
{
{ config, response } = error;
(response && response. === && !config.) {
(isRefreshing) {
( {
( {
config.[] = ;
((config));
});
});
}
config. = ;
isRefreshing = ;
api.(, { : () })
.( {
{ accessToken, refreshToken } = res.;
({ accessToken, refreshToken });
isRefreshing = ;
(accessToken);
config.[] = ;
(config);
})
.( {
isRefreshing = ;
.();
.. = ;
.(err);
});
}
.(error);
}
);
api;


