接手老项目,需要写一个access_token刷新的逻辑,具体流程我就不赘述了,网上关于JWT刷新流程的文章有很多。我遇到的主要问题是,项目没有使用axios,原生的fetch没有拦截器,对于多次同时刷新token的请求是应该做拦截处理的,待第一个刷新请求回调后再发起后续被拦截请求,业务场景和这篇文章类似,难点在于如何挂起请求,直接贴代码。
let isRefreshing = false; // 用于拦截鉴权失败的请求
let pendingRequests = []; // 被拦截请求的缓存池
// 持久化token,我是写cookie里的
const storeToken = function (data) {
const { access_token, refresh_token } = data;
const duration = 60 * 60 * 1; // 持续时间-秒
ctx.app.$cookie('accessToken', access_token, { expires: duration });
ctx.app.$cookie('refreshToken', refresh_token, { expires: duration });
};
const refreshToken = async function () {
isRefreshing = true;
try {
// 换取token的请求
const res = await $jfetch.post('/japi/v1/auth?grant_type=refresh_token', {
body: {
refresh_token: ctx.app.$cookie('refreshToken'),
},
});
storeToken(res.data);
isRefreshing = false;
const newAccesssToken = res.data.access_token;
// 用新的token重新发起待定池中的请求
pendingRequests.forEach((item) => {
item.resolved(newAccesssToken);
});
// 清空缓存池
pendingRequests = [];
return newAccesssToken;
} catch (error) {
isRefreshing = false;
return null;
}
};
const getCookieToken = async function () {
// 避免重复发起刷新
if (isRefreshing) return;
const accessToken = ctx.app.$cookie('accessToken');
if (!accessToken) {
return await refreshToken();
}
return accessToken;
};
const getAccessToken = async function () {
// 取到为空的表示是该被拦截的
const accessToken = await getCookieToken();
// 将被拦截的请求挂起 存到缓存池中
if (!accessToken) {
// 重点
const externalControl = {
resolved: null,
};
// 这里返回了一个新的Promise变相的实现请求的挂起(只要没有resolved或rejected,请求就会一直处于pedding状态)
// 并将Promise状态的改变放到了外部一个对象来控制 externalControl ,待定池缓存这个对象即可,待需要执行后续被拦截请求,只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
const interceptPromise = new Promise((resolved) => {
externalControl.resolved = resolved;
});
pendingRequests.push(externalControl);
return interceptPromise;
}
return accessToken;
};
在需要鉴权的接口调用,这里还缺少refresh_token失效跳转到登录页的逻辑,自行填补:
headers['Authorization'] = await getAccessToken();