背景需求
客户端登录请求成功后,服务器将用户信息(如用户id)使用特殊算法加密后作为验证的标志发送给用户(即token),当用户下次发起请求时,会将这个token捎带过来,服务器再将这个token通过解密后进行验证,通过的话,则向客户端返回请求的数据;反之,则请求失败
问题表现
一般为了安全性,token都会设置一个过期时间,在过期之后就无法请求相关接口了,这时应该怎么办呢,是直接退出登录吗?
解决方案
方案一
后端返回过期时间,前端判断token过期时间,去调用刷新token接口
方案二
写个定时器,定时刷新token接口
方案三
在请求拦截器中拦截,判断token是否将要过期,调用刷新token接口
方案一需要后端提供接口,客户端请求增加带宽,方案二客户端一直刷新,造成资源浪费,为了更好的用户体验,我们选择方案三,在拦截器里拦截刷新token
具体操作
登录请求成功后,会返回一个token和token过期时间,在每次请求api时,前端可以先判断一下token是否即将过期或已过期,如果是,则请求刷新token的接口,成功替换原来的token之后才可以重新发起请求
...
// 是否有请求正在刷新token
window.isRefreshing = false;
// 被挂起的请求数组
let refreshSubscribers = [];
// 刷新请求(refreshSubscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据)
function onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token));
}
// push所有请求到数组中
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
// http request拦截
axios.interceptors.request.use(config => {
// 开启 progress bar
NProgress.start();
const meta = (config.meta || {});
const isToken = meta.isToken === false;
config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`;
// 判断token是否将要过期
if (isTokenExpired()) {
// 判断是否正在刷新
if (!window.isRefreshing) {
// 将刷新token的标志置为true
window.isRefreshing = true;
// 发起刷新token的请求
this.$store
.dispatch("refreshToken")
.then(res => {
// 将标志置为false
window.isRefreshing = false;
// 执行数组里的函数,重新发起被挂起的请求
onRrefreshed(res.data.data.token);
/*执行onRefreshed函数后清空数组中保存的请求*/
refreshSubscribers = [];
})
.catch(() => {
/*将标志置为false*/
window.isRefreshing = false;
});
}
/*把请求(token)=>{....}都push到一个数组中*/
let retry = new Promise((resolve) => {
/*(token) => {...}这个函数就是回调函数*/
subscribeTokenRefresh(() => {
config.headers.Authorization = "Bearer " + getToken();
/*将请求挂起*/
resolve(config);
});
});
return retry;
}
return config
}, error => {
return Promise.reject(error)
});
...
复制代码