为什么要用双Token无感刷新,它解决了什么问题?
为了保证安全性,后端设置的Token不可能长期有效,过了一段时间Token就会失效。而发送网络请求的过程又是需要携带Token的,一旦Token失效,用户就要重新登陆,这样用户可能需要频繁登录,体验不好。为了解决这个问题,采取双Token(Access_Token,Refresh_Token)无感刷新,用户完全体会不到Token的变化,但实际上,Token已经刷新了。
这两个Token都是干什么的?具体过程是怎样的?
前置条件:
后端提供一个刷新Token的接口,假设是A接口。
两个Token的作用:
Access_Token:用于鉴定用户身份,即每次发送网络请求都需要携带这个Access_Token
Refresh_Token:用于刷新Access_Token,即调用A接口需要携带Refresh_Token, 用它换得最新的Access_Token
过程:
1.用户登录客户端之后,接收后端发送的两个Token,前端把它们存在localStorage或vuex中。
2.Access_Token未过期时,发送网络请求携带Access_Token即可
3.Access_Token过期后,前端携带Refresh_Token调用A接口得到新的Access_Token,把新的Access_Token替换旧的Access_Token存储起来。
具体如何实现?
首先先明确一点,这个无感刷新的逻辑要写在axios的响应拦截器里面。
说明:
isRefreshing用于标记是否正在调用tokenRefreshAPI,相当于一把锁
requests队列用于存储tokenRefreshAPI响应期间进入的promise对象。
var isRefreshing = false,
//重试队列
requests = [],
说明:
service是自己配置的axios,在axios里面配置一些基本设置,比如baseURL,timeout,headers的Authorization(一般是"Bearer"+Access_Token)
service.interceptors.response.use(
res => {
//accesstoken超时
if (res.data.result_status === 30002) {
//记录当前res
let response = res;
if (!isRefreshing) {
isRefreshing = true;
return tokenRefreshAPI({
token: JSON.parse(window.localStorage.token)
}).then((res) => {
//refreshToken没过期,刷新成功
console.log('tokenRefreshAPI', res.data);
if (res.data.result_status === 0) {
console.log('token刷新成功', res.data);
//重新设置token
window.localStorage.setItem('token', JSON.stringify(res.data.token));
//执行失效的函数
requests.forEach((cb) => cb());
requests = []; // 重新请求完清空
return service(response.config);
} else if (res.data.result_status === 30011) {
console.log('refreshToken过期,请重新登录');
window.localStorage.clear();
router.push('/login');
}
}).finally(() => {
isRefreshing = false;
});
} else {
console.log('token过期,剩余请求存入队列', axiosConfig.url);
// 返回未执行 resolve 的 Promise
return new Promise(resolve => {
// 用函数形式将 resolve 存入,等待刷新后再执行
requests.push(() => {
resolve(service(axiosConfig));
});
});
}
}
return res;
})
注意:
在Access_Token过期后,可能会短时间发送很多个网络请求,这些网络请求拦截器全都会请求刷新Token,这样显然是不合理的。其实只要刷新一次就够了,所以给刷新Token这步操作加一个锁,这也就是为什么要设置isRefreshing的原因。
在请求刷新Token的响应时间内,可能会有多个网络请求到达,我们又暂时没办法处理这些网络请求,所以我们先把这些网络请求promise对象存储到队列requests里面,等到刷新好Token之后再依次处理这些promise对象。