一、什么情况下需要做token刷新?
背景:单点登录使用token方案
目的:减轻服务器压力
双token的好处:具有权限控制的能力
在单点登录情况下,使用token做权限验证,可以减轻服务器的压力。使用两个token,accessToken和refreshToken。当使用accessToken访问 其他服务过期时,需要去CAS 服务刷新token,然后再去请求业务服务器。
控制refreshToken,就可以达到踢人下线等目的。
CAS (Central Authentication Service)中心授权服务
单点登录(SSO:Single Sign On
用户只需要登录一次,就可以在多个系统中进行访问。类似于在百度系网站中,你在百度网页登录了百度账号,旗下的贴吧,网盘等不需要再次登录,就可以使用。
二、如何实现token刷新?
优化:使用队列优化未发出的请求
当已经出现未授权错误时,之后进入的请求都放入到一个队列中,当刷新token成功后,修改token,再发出,达到减轻服务器压力的目的
import { isAxiosError, type AxiosError, type AxiosResponse } from 'axios';
import type { FReqConfig, ReqItem, SetupRefreshTokenOptions } from './type';
export function useRefreshToken({
refreshToken,
unauthorizedErrorHandler,
http,
setToken,
getIsUnauthorized
}: SetupRefreshTokenOptions) {
let promise: Promise<any> | null = null;
let isRefreshToken = false;
/**
* ## 收集401失败请求,如果没有正在刷新token,则刷新
* @param resp
*/
async function handleUnauthorized(resp: AxiosResponse | AxiosError) {
const { status, config } = resp || {};
let isUnauthorized = false;
const isError = await isAxiosError(resp);
if (isError) {
isUnauthorized = status === 401;
} else {
if (typeof getIsUnauthorized == 'function') {
isUnauthorized = getIsUnauthorized(resp);
}
}
// 如果当前正在刷新中,则返回promise
if (isUnauthorized) {
if (!isRefreshToken) _refreshToken();
const isSuccess = await promise;
if (isSuccess) {
return http.request(setToken(config!));
} else {
if (typeof unauthorizedErrorHandler === 'function') unauthorizedErrorHandler();
return Promise.reject(resp);
}
}
// 不是未授权错误
if (isError) {
return Promise.reject(resp);
} else {
return Promise.resolve(resp);
}
}
/** ## 收集 401 后发起的请求,等token刷新后再执行 */
let queue: ReqItem[] = [];
/**
* # 当刷新token时,收集将要执行的请求
* @param config
* @returns axiosConfig
*/
function handleReqWhenUnauthorized(config: FReqConfig): Promise<FReqConfig> {
if (isRefreshToken) {
return new Promise((resolve) => {
queue.push({ resolve, config, type: 'request' });
});
}
return Promise.resolve(config);
}
/**
* ## 处理刷新token的逻辑
* 1. 所有的401 请求都使用一个 刷新token的 promise
* 2. 刷新token成功后,重新执行因为401 而没有发起的请求
*/
async function _refreshToken() {
isRefreshToken = true;
if (!promise) {
promise = refreshToken();
}
promise.then((isSuccess) => {
if (isSuccess)
queue.forEach((item) => {
item.resolve(setToken(item.config));
});
});
// 重置刷新标识, 刷新的promise, 和 待发送请求的队列
promise.finally(() => {
isRefreshToken = false;
promise = null;
queue = [];
});
}
return { handleUnauthorized, handleReqWhenUnauthorized };
}