需求背景
按以前的做法,前端在token过期后会跳回登录页,要求重新登录,这样会打断用户操作,所以提出需求:要无感刷新token
代码设计背景
1.后端:每个用户的token存入user表里这个用户的token字段,每次登录即更新token字段;
2.前端:利用axios发请求
3.前后端共用:前端把token存在headers的acces_token中,每个请求后端都进行校验token是否过期(实现方式看2020-11-04 node端做用户token校验(jsonwebtoken)),token过期返回特定code供前端在拦截器里做判断。
设计思路
在返回拦截器判断token是否过期,如果过期,重新发送登录请求以刷新token,然后重新发起token过期时发送的请求。
具体实现思路可查看【axios如何利用promise无痛刷新token】
返回拦截器代码
最终实现目标:
1.拦截器收到token过期的code,请求登录接口,成功后再请求过期时发送的请求
2.防止多次刷新token
3.解决多个请求的重新发送
const axios = Axios.create(axios_config); // 创建axios
const REFRESH_TOKEN_CODE = 996; // 和后端统一定义996是token过期的code
// 响应拦截器
let isRefreshing = false; // 是否正在刷新token
let requests = []; // 等待token刷新后重新发送的请求队列
axios.interceptors.response.use(
async res => {
// 返回成功有code的情况,返回 res.data.data
if (res.data && res.data.code) {
if (res.data.code === 200) {
return res.data.data != undefined ? res.data.data : {}; // code 200 也可能没有 data
} else {
// 自动刷新token逻辑
const config = res.config; // 本次失败请求的信息
if (res.data.code === REFRESH_TOKEN_CODE) {
if (!isRefreshing) {
isRefreshing = true;
try {
const userInfo = JSON.parse(sessionStorage.getItem('userInfo')); // 从缓存中读取用户信息,用于登录
const params = {
... // 登录请求的参数
};
const loginRes = await login(params); // 登录请求
sessionStorage.setItem('userInfo', JSON.stringify(loginRes)); // 再存入缓存
config.headers['access_token'] = loginRes.token;
// 已经刷新了token,将所有队列中的请求进行重试
requests.forEach(cb => cb(loginRes.token));
requests = []; // 重试完了别忘了清空这个队列
return axios(config); // 第一个重新发送的请求,其他请求都在else中
} catch (e) {
console.log(e);
vm.$router.push({ path: '/login' });
} finally {
isRefreshing = false;
}
} else {
// 正在刷新token,返回一个未执行resolve的promise
return new Promise(resolve => {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
requests.push(token => {
config.headers['access_token'] = token;
resolve(axios(config));
});
});
}
return;
}
vm.$message.closeAll();
vm.$message.error(res.data.msg);
// if (LOGOUT_RES_CODE.includes(res.data.code)) {
// vm.$router.push({ path: '/login' });
// }
return Promise.reject({
code: res.data.code,
msg: res.data.msg || '',
});
}
}
return res.data;
},
error => {
... // 省略部分代码
return Promise.reject(error);
}
);