需求背景
在实现单点登录时,结合使用token无感刷新,提升用户体验。
调用接口时,如果token过期(即接口返回401),则去调用refreshtoekn接口去刷新token,此时如果sessionId有效则返回新token,并重新发起当前请求,实现无感刷新token;如果sessionId失效则会返回401。
解决思路
当请求返回401时,表示token过期,此时需要用旧token换新token,如果拿到新token,则存储并设置新token后,重发请求
- 需要考虑并发情况,即多个请求返回401。设置一个字段(isRefreshing)来作为一个“锁”,数组(failedQueue)来存储发送失败的请求
如果refreshtoken返回401的话,走正常token过期登出处理逻辑。
具体实现
在响应拦截器中对401情况进行统一处理
// 是否正在进行刷新token
let isRefreshing = false
// 失败请求队列
let failedQueue = []
service.interceptors.response.use(async res => {}, async error => {
...
if (error.response.status === 401) {
if(config.url==='/sso/refresh'){
// 当refreshtoken返回401时,走正常登出流程
// 比如:弹框提示 -> logout -> 清空token -> 跳转首页
...
}
}else{
// 如果正在刷新Token,将当前请求加入队列,等待Token刷新完毕
if(isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject, originalRequest:error.config});
})
}
isRefreshing = true
return refreshToken().then(res=>{
const newToken = res.token;
setToken(newToken)
service.defaults.headers['Authorization'] = `Bearer ${newToken}`;
// 处理失败的队列请求
failedQueue.forEach((req) => req.resolve(service(req.originalRequest)));
// 清空队列
failedQueue = [];
// 重发当前请求
return service(error.config);
}).catch(err=>{
failedQueue.forEach((req) => req.reject(err));
failedQueue = [];
return Promise.reject(err);
}).finally(()=>{
isRefreshing = false;
})
}
...
})