思路
Token无感知刷新是一种常见的技术,就是在用户无感知的情况下自动处理Token过期的问题,避免用户因Token过期而被迫重新登录。以下是实现Token无感知刷新的主要步骤和考虑因素:
一、Token生成与存储
- Token生成:
- 在用户登录成功后,后端会生成两个Token:一个AccessToken(用于访问受保护的API,过期时间较短)和一个RefreshToken(用于获取新的AccessToken,过期时间较长)。
- 可以使用JWT(JSON Web Tokens)或其他安全机制来生成和验证Token。
- Token存储:
将AccessToken和RefreshToken存储在客户端的本地缓存中,如localStorage或sessionStorage。
确保RefreshToken的安全性,避免在客户端以明文形式暴露。
二、请求拦截器设置
- 请求拦截器:
- 在发送请求之前,通过请求拦截器检查AccessToken是否存在并未过期。
- 如果AccessToken存在且未过期,则将其添加到请求的Authorization头部。
- 如果AccessToken不存在或已过期,则尝试使用RefreshToken获取新的AccessToken。
- 响应拦截器:
在接收到响应后,通过响应拦截器检查响应状态码。
如果状态码为401(未授权),则表明AccessToken已过期,此时应使用RefreshToken尝试获取新的AccessToken。
如果状态码为200(成功)或其他有效状态码,则直接处理响应数据。
三、Token刷新逻辑
- 检查Token是否过期:
- 可以在请求拦截器中检查AccessToken的过期时间,但这需要后端提供Token的过期时间字段,且存在本地时间被篡改的风险。
- 更推荐的做法是在响应拦截器中根据状态码(如401)来判断AccessToken是否过期。
- 使用RefreshToken获取新Token:
当检测到AccessToken过期时,使用RefreshToken向认证服务器发送请求以获取新的AccessToken和(可选的)新的RefreshToken。
将新获取的AccessToken保存到本地缓存,并替换掉旧的AccessToken。
重新发送请求:
使用新的AccessToken重新发送之前因Token过期而失败的请求。
这可能需要将失败的请求暂存起来,并在获取到新Token后依次重新发送。
四、防止多次刷新Token
- 设置一个标志位(如isRefreshing)来指示当前是否正在刷新Token。
- 如果在刷新Token的过程中又收到了需要刷新Token的请求,则可以直接使用已获取的(或正在获取的)新Token,而不是再次发起刷新Token的请求。
实现
前端实现Token无感知刷新的过程主要涉及到对HTTP请求的拦截、Token状态的判断、Token的刷新以及请求的重发等步骤。以下是一个详细的实现流程:
一、Token的获取与存储
- 用户登录:
- 用户输入用户名和密码进行登录。
- 登录成功后,后端服务器会生成一个AccessToken(短期Token)和一个RefreshToken(长期Token),并将它们返回给前端。
- 存储Token:
前端将AccessToken和RefreshToken存储在浏览器的本地缓存中,如localStorage或sessionStorage。由于localStorage具有持久性,更适合存储RefreshToken;而sessionStorage在页面会话结束时会被清除,适合存储AccessToken(但考虑到需要跨会话保持登录状态,通常也会选择localStorage)。
二、请求拦截器的设置
- 创建Axios实例:
- 使用Axios等HTTP客户端库创建一个Axios实例,并配置基础URL、请求超时时间等。
- 设置请求拦截器:
在发送请求之前,通过请求拦截器检查localStorage中是否存储了有效的AccessToken。
如果存在,则将AccessToken添加到请求的Authorization头部。
设置响应拦截器:
在接收到响应后,通过响应拦截器检查响应状态码。
如果状态码为401(未授权),则表明AccessToken已过期,此时需要尝试使用RefreshToken刷新Token。
三、Token刷新逻辑
- 检查Token是否过期:
- 响应拦截器中,根据状态码401判断AccessToken是否过期。注意,更准确的做法是在响应体中包含Token过期的具体信息,但这里以状态码为例。
- 使用RefreshToken获取新Token:
发起一个POST请求到认证服务器,将RefreshToken作为请求体或请求头发送给后端。
后端验证RefreshToken的有效性,并返回一个新的AccessToken(和可选的新的RefreshToken)。
前端接收到新的AccessToken后,将其保存到localStorage中,并替换掉旧的AccessToken。
重新发送请求:
使用新的AccessToken重新发送之前因Token过期而失败的请求。
这可以通过将失败的请求暂存起来,并在获取到新Token后依次重新发送来实现。
四、防止多次刷新Token
- 在刷新Token的过程中,设置一个标志位(如isRefreshing)来指示当前是否正在刷新Token。
- 如果在刷新Token的过程中又收到了需要刷新Token的请求,则可以直接使用已获取的(或正在获取的)新Token,而不是再次发起刷新Token的请求。
五、代码示例(简化版)
由于篇幅限制,这里只提供一个简化的代码示例框架:
import axios from 'axios';
// 创建axios实例
const service = axios.create({
baseURL: 'http://your-api-url', // API的base_url
timeout: 5000 // 请求超时时间
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 从localStorage获取token,并设置到请求头中
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
error => {
// 处理请求错误
console.error('请求错误:', error); // for debug
Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data;
},
error => {
// 处理响应错误
if (error.response && error.response.status === 401) {
// 尝试使用RefreshToken刷新Token
return refreshToken().then(newToken => {
// 设置新的Token并重新发送请求
localStorage.setItem('token', newToken);
// 这里需要实现请求重发的逻辑,可以通过修改Axios实例的配置或使用其他方式
}).catch(err => {
// 刷新Token失败,可能需要用户重新登录
console.error('刷新Token失败:', err);
});
}
return Promise.reject(error);
}
);
// 刷新Token的函数(需要实现)
function refreshToken() {
// 发送请求到认证服务器获取新的Token
// 返回Promise,解析为新的Token
}
上述代码示例是一个简化的框架,实际实现时需要根据具体业务需求和后端API进行相应的调整和完善。特别是刷新Token的函数