无感刷新Token是一种技术手段,能让用户在使用网站或APP时,不用频繁登录,系统自动帮你续命,体验更加流畅。下面用最简单的语言和丰富的代码示例,帮你彻底理解它的原理和实现。
什么是Token?
- Token是一种“身份凭证”,类似于你进门的通行证。
- 用户登录后,服务器发给客户端一个Token,客户端每次请求时带上它,服务器用它确认你的身份。
- Token有有效期,过期后就不能用了。
为什么需要“刷新Token”?
- Token有效期短是为了安全,比如15分钟。
- 如果Token过期了,用户就得重新登录,体验很差。
- 解决办法是用另一个叫Refresh Token的长期凭证,自动帮你换新的短期Token,用户感觉不到。
“无感刷新Token”是啥?
- 当短期Token(access token)过期时,系统自动用长期Token(refresh token)换一个新的短期Token。
- 换完后,自动重试之前失败的请求。
- 用户完全感觉不到,不用重新登录或刷新页面。
形象比喻:地铁票
- 短期票(access token):有效期15分钟,刷票进站。
- 长期通行证(refresh token):有效期长,可以换短期票。
- 票过期时,系统自动帮你换票,你继续刷闸机进站,无感知。
- 如果长期通行证也过期了,才需要重新买票(登录)。
技术原理和流程
双Token机制
| Token类型 | 作用 | 有效期 |
|---|---|---|
| Access Token | 请求接口的凭证 | 短(分钟级) |
| Refresh Token | 刷新Access Token用 | 长(天或月级) |
请求流程
-
请求接口时,带上Access Token。
-
服务器验证Token:
- 如果有效,正常返回数据。
- 如果过期,返回401或特定错误码。
-
前端捕获过期状态,自动用Refresh Token请求刷新接口。
-
刷新成功,拿到新Access Token,更新本地存储。
-
重新发起之前失败的请求。
-
如果Refresh Token也过期,跳转登录页。
伪流程图
text
[客户端发请求]
|
[Access Token有效?]
/ \
是 否
| |
请求接口 [Refresh Token有效?]
/ \
是 否
| |
刷新Access Token 跳转登录
|
重新发起请求
关键实现点
1. 前端拦截器自动刷新Token
- 请求发出前或响应回来后,判断Token是否过期。
- 过期时发起刷新请求。
- 刷新期间,其他请求排队等待刷新完成。
- 刷新成功后,继续执行排队请求。
2. 滑动过期机制
- Refresh Token存在Redis,设置过期时间(如7天)。
- 每次刷新时,延长Redis中Refresh Token的过期时间,保持活跃。
- Redis过期即表示Refresh Token失效。
3. 安全性
- 不完全信任JWT自带的过期时间,以Redis的过期状态为准。
- 即使Token被盗,没有Redis配合也无法使用。
代码示例:基于axios的无感刷新Token实现(Vue/React通用)
import axios from 'axios';
// 创建axios实例
const api = axios.create({
baseURL: '/api',
headers: { 'Content-Type': 'application/json' }
});
let isRefreshing = false; // 是否正在刷新Token
let requestQueue = []; // 等待刷新完成的请求队列
// 获取本地Token
function getAccessToken() {
return localStorage.getItem('accessToken');
}
function getRefreshToken() {
return localStorage.getItem('refreshToken');
}
// 保存新Token
function setTokens({ accessToken, refreshToken }) {
if (accessToken) localStorage.setItem('accessToken', accessToken);
if (refreshToken) localStorage.setItem('refreshToken', refreshToken);
}
// 刷新Token接口
function refreshToken() {
return axios.post('/auth/refresh-token', {
refreshToken: getRefreshToken()
});
}
// 请求拦截器:每个请求带上Access Token
api.interceptors.request.use(config => {
const token = getAccessToken();
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
});
// 响应拦截器:处理Token过期
api.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config;
if (error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 刷新中,把请求加入队列,等待刷新完成
return new Promise((resolve) => {
requestQueue.push(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
resolve(api(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
// 发起刷新Token请求
return refreshToken().then(res => {
const { accessToken, refreshToken } = res.data;
setTokens({ accessToken, refreshToken });
api.defaults.headers.common['Authorization'] = 'Bearer ' + accessToken;
// 执行队列中的请求
requestQueue.forEach(cb => cb(accessToken));
requestQueue = [];
isRefreshing = false;
// 重新发起原始请求
originalRequest.headers['Authorization'] = 'Bearer ' + accessToken;
return api(originalRequest);
}).catch(() => {
isRefreshing = false;
requestQueue = [];
// 刷新失败,跳转登录
window.location.href = '/login';
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
实践中常见的数值指标
- Access Token有效期:10-30分钟(根据安全需求调整)
- Refresh Token有效期:7天-30天
- Redis滑动过期时间:每次刷新时延长7天
- 请求重试队列长度:根据并发请求量调整,通常几十条即可
总结
- 无感刷新Token技术让用户登录体验更顺畅,避免频繁登录。
- 采用短期Access Token和长期Refresh Token双机制,兼顾安全和体验。
- 通过前端拦截器自动刷新Token,后台滑动过期管理Refresh Token。
- 代码实现中注意处理并发刷新和请求排队。
- 安全上依赖Redis控制Token有效性,防止被盗用。
掌握无感刷新Token,是现代前后端分离应用提升用户体验和安全性的关键技术。希望本文的讲解和示例能帮你快速理解和实现这一机制。