在现代Web应用中,身份认证是保障系统安全
的重要环节
。基于Token的认证机制因其灵活性、跨域支持等优势被广泛采用。然而,Token的过期问题一直是开发者需要面对的挑战。本文将深入探讨如何实现Token
的无感刷新
机制,提升用户体验的同时保障系统安全性。
🔴 背景与核心概念
🔻 单点登录环境下的Token机制
无感刷新技术必须建立在单点登录(SSO)
环境下才有实际意义。在SSO系统
中,用户只需登录一次
即可访问多个相互信任
的应用
系统。这种环境下通常会使用两种Token
:
- Access Token(访问令牌):有效期较短(通常10-20分钟),用于访问受保护资源
- Refresh Token(刷新令牌):有效期较长(通常7天至1个月),用于获取新的Access Token
🔻 为什么需要无感刷新?
当Access Token过期时,传统做法是强制用户重新登录,这会导致:
- 频繁
中断用户操作
流程 降低用户
体验- 增加用户
认证负担
无感刷新技术正是为了解决这些问题而诞生,它能够在用户无感知
的情况下自动完成Token
的更新
。
🔴 技术实现方案
🔻 基础架构设计
实现无感刷新需要前后端协同工作,但主要逻辑集中在客户端(前端):
- 登录成功时 :客户端
接收
并存储Access Token
和Refresh Token
- API请求时 :
携带
当前有效的Access Token
- Token过期时 :
自动
使用Refresh Token
获取新的Access Token
- 重新请求 :用
新Token
重试原始请求
🔻 前端实现代码解析
以下是一个基于Axios的实现示例:
javascript
// 创建axios实例
const instance = axios.create({
baseURL: 'https://api.example.com'
});
// 请求拦截器 - 添加Token到请求头
instance.interceptors.request.use(config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器 - 处理Token刷新逻辑
instance.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
// 判断是否为401错误且不是刷新Token的请求
if (error.response.status === 401 && !originalRequest._isRetry) {
originalRequest._isRetry = true;
try {
// 尝试刷新Token
const { data } = await refreshToken();
// 存储新Token
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
// 修改原始请求的Token头
originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
// 重新发起原始请求
return instance(originalRequest);
} catch (refreshError) {
// 刷新失败,跳转登录页
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
// 刷新Token的函数
let refreshPromise = null;
async function refreshToken() {
if (!refreshPromise) {
const refreshToken = localStorage.getItem('refresh_token');
refreshPromise = axios.post('/auth/refresh', { refresh_token: refreshToken })
.finally(() => {
refreshPromise = null; // 无论成功失败都重置
});
}
return refreshPromise;
}
🔴 关键问题与解决方案
🔻 1. 防止重复刷新
当多个请求同时
遇到Token过期
时,需要防止并发刷新请求
:
解决方案 :使用Promise缓存机制
,确保同一时间只有一个刷新请求在进行
。
javascript
let refreshPromise = null;
async function refreshToken() {
if (!refreshPromise) {
const refreshToken = localStorage.getItem('refresh_token');
refreshPromise = axios.post('/auth/refresh', { refresh_token })
.finally(() => {
refreshPromise = null; // 重置Promise
});
}
return refreshPromise;
}
🔻 2. 避免无限循环
当Refresh Token也过期时,系统可能陷入无限刷新循环:
解决方案:区分普通请求和刷新请求,仅对普通请求执行刷新逻辑。
javascript
// 在刷新请求中添加特殊标记
async function refreshToken() {
const config = {
_isRefresh: true // 自定义标记
};
return axios.post('/auth/refresh', { refresh_token }, config);
}
// 在拦截器中检查
if (error.response.status === 401 && !originalRequest._isRefresh) {
// 执行刷新逻辑
}
🔻 3. 竞态条件处理
当第一个刷新请求未完成时,后续请求应等待而非发起新的刷新:
解决方案:使用共享的Promise实例,所有等待中的请求共享同一个刷新结果。
🔴 进阶优化方案
🔻 1. 跨标签页同步
多标签页环境下,应共享刷新状态:
javascript
// 使用BroadcastChannel API同步状态
const channel = new BroadcastChannel('token_refresh');
channel.onmessage = (event) => {
if (event.data.type === 'token_updated') {
// 更新本地Token
localStorage.setItem('access_token', event.data.token);
}
};
// 刷新成功后广播通知
channel.postMessage({
type: 'token_updated',
token: newToken
});
🔻 2. 预刷新机制
在Token接近过期时提前刷新,避免请求失败:
javascript
function checkTokenExpiry() {
const token = localStorage.getItem('access_token');
const { exp } = decodeToken(token); // 解析JWT获取过期时间
const now = Date.now() / 1000;
// 在过期前5分钟触发刷新
if (exp - now < 300) {
refreshToken();
}
}
// 定时检查(如每分钟一次)
setInterval(checkTokenExpiry, 60000);
🔴 安全注意事项
- Refresh Token保护:应严格保护Refresh Token,建议设置HttpOnly、Secure和SameSite属性
- 单次使用:Refresh Token应设计为单次使用,刷新后失效
- 绑定设备:Refresh Token可与设备指纹绑定,增加安全性
- 撤销机制:提供管理员接口可主动撤销Refresh Token
🔴 后端实现
下载jwt
包,配置redis
:
-
登录流程:
- 用户提交凭证 → 验证通过 → 返回双Token
- Access Token用于常规请求(短有效期)
- Refresh Token用于获取新Access Token(长有效期)
-
无感刷新流程:
- 前端检测到401错误 → 使用Refresh Token获取新Access Token
- 自动重试原始请求 → 用户无感知
-
安全机制:
- Refresh Token白名单验证
- Token黑名单机制
- 短期Access Token减少泄露风险
📢:所有相关token
的操作都使用redis
进行存储和判断以及删除。
🔴 总结
Token无感刷新技术是提升现代Web应用用户体验的关键技术之一。通过合理的架构设计和细致的异常处理,可以实现安全、稳定的自动刷新机制。本文介绍的核心实现方案和优化技巧,可以帮助开发者构建更加健壮的身份认证系统。
值得强调的是,无感刷新必须与单点登录系统配合使用才有实际意义。在实际项目中,开发者还需要根据具体业务需求和安全要求进行适当调整和扩展。