在现代前端开发中,使用 API 进行数据交互时,我们常常会遇到身份认证的问题。为了提高安全性,许多应用采用了Token 机制,如JWT(JSON Web Token) 来管理用户的身份状态。本文将介绍如何在 Vue3 项目中使用 Axios 实现双 Token 刷新机制,确保用户体验流畅的同时提高安全性。
前置条件
- 本文只针对Vue3前端所编写的双Token刷新解决方案,关于Spring Cloud微服务项目的双Token刷新实现,请参考:Spring Cloud + JWT实现双Token刷新
- 关于Vue3项目的搭建,请参考:Vite 创建 Vue3 + TS 项目
- 本文将通过封装Axios的方式,让前端实现无感刷新Token,关于Axios的封装,请参考:Vue3项目基于Axios封装request请求
刷新机制
在完成上述步骤后,我们就可以开始对前端项目进行操作了。但首先,我们需要了解到双Token刷新的机制:
在使用 Token 认证时,我们通常会使用两种Token:
- **Access Token:**用于身份验证,通常时效较短(如 10 分钟)。
- Refresh Token:用于获取新的 Access Token,时效较长(如 7 天至几个月)。
当 Access Token 过期时,我们可以使用Refresh Token 来请求新的Access Token,而不是要求用户重新登录。通过这种方式,用户的体验将更加平滑。
但是,这里是博主自己写的后端,博主的服务端在双Token刷新机制上的原理跟上述是一样的, 不同的是,博主在生成Token的时候,并没有将Refresh Token 返回给前端,而是跟UserId 一起以键值对的形式存储在了Redis中。
在客户端Access Token 过期后,直接根据Base64 解析出Access Token 载荷中的UserId ,然后根据这个UserId 查询存储在Redis 中的Refresh Token ,如果这个Refresh Token 有效且是合法的,那么我们就根据之前Access Token 载荷中的信息重新生成一个Access Token 返回给客户端,以此来达到刷新Token的目的。
根据以上Token 刷新机制,我们在Vue3 前端代码中,可以给出一个无感刷新Token的思路:
- 如果Access Token 过期,那么我们可以通过Axios 的响应拦截器获取到新的Access Token
- 如果获取到的这个Access Token存在且不为空,我们可以重新发送原始请求
代码实现
javascript
// 添加响应拦截器
service.interceptors.response.use(
async (response) => {
// 判断是否有新的Token
if (response.data.ACCESS_TOKEN) {
// 将服务端返回的新Token存储到Session中
Session.set('token', response.data.ACCESS_TOKEN);
// 重新发送原始请求
const config = response.config;
try {
const newResponse = await service.request(config);
return newResponse;
} catch (error) {
return Promise.reject(error);
}
}
// 对响应数据做点什么
const res = response.data;
if (res.code && res.code !== 0) {
// `token` 过期或者账号已在别处登录
if (res.code === 401 || res.code === 4001) {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
.then(() => { })
.catch(() => { });
return Promise.reject(service.interceptors.response);
} else { // 如果响应中有新的 token,则更新 Session 中的 token
return res;
}
} else {
return res;
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf('timeout') != -1) {
ElMessage.error('网络超时');
} else if (error.message == 'Network Error') {
ElMessage.error('网络连接错误');
} else {
if (error.response.data) ElMessage.error(error.response.statusText);
else ElMessage.error('接口路径找不到');
}
return Promise.reject(error);
}
);
检验真理
如图,当Token过期时,我们访问这个查询接口时,Axios进行了两次接口调用
第一次调用这个查询接口时,我们的Token失效了,拿到了服务端给的新的Token
然后,根据我们之前在Axios响应拦截器中的代码逻辑,它在将这个新的Token存储到Session中后,又重新发送了一次原始请求