fix(web): 修复 401 鉴权恢复跳转

This commit is contained in:
sunlei 2026-05-17 15:32:10 +08:00
parent 99f5615144
commit c54a7f4f36
2 changed files with 69 additions and 7 deletions

View File

@ -18,6 +18,7 @@ const ACCESS_CODES_KEY = "kt-admin-access-codes";
const USER_INFO_KEY = "kt-admin-user-info"; const USER_INFO_KEY = "kt-admin-user-info";
let refreshPromise: Promise<string | null> | null = null; let refreshPromise: Promise<string | null> | null = null;
let redirectingToAdminLogin = false;
const authClient = axios.create({ const authClient = axios.create({
baseURL: config.axiosBase, baseURL: config.axiosBase,
@ -66,6 +67,9 @@ const buildAdminLoginUrl = (redirect: string) => {
}; };
export const redirectToAdminLogin = () => { export const redirectToAdminLogin = () => {
if (redirectingToAdminLogin) return;
redirectingToAdminLogin = true;
window.location.href = buildAdminLoginUrl(window.location.href); window.location.href = buildAdminLoginUrl(window.location.href);
}; };

View File

@ -10,9 +10,18 @@ import {
export interface ApiResponse<T = any> { export interface ApiResponse<T = any> {
code: number; code: number;
data: T; data: T;
message?: string;
msg: string; msg: string;
} }
type AuthRetryConfig = AxiosRequestConfig & {
_authRetried?: boolean;
};
type AuthHeaderMap = Record<string, unknown> & {
get?: (name: string) => unknown;
};
const request = axios.create({ const request = axios.create({
baseURL: config.axiosBase, baseURL: config.axiosBase,
timeout: 1000 * 30, timeout: 1000 * 30,
@ -27,6 +36,49 @@ export const post = <T = any>(url: string, data?: any, config?: AxiosRequestConf
return request.post<any, ApiResponse<T>>(url, data, config); return request.post<any, ApiResponse<T>>(url, data, config);
}; };
const getAuthErrorMessage = (data?: Partial<ApiResponse>) => {
return data?.msg || data?.message || "登录已过期";
};
const getRequestAuthorization = (requestConfig?: AuthRetryConfig) => {
const headers = requestConfig?.headers as AuthHeaderMap | undefined;
if (!headers) return null;
if (typeof headers.get === "function") {
return headers.get("Authorization") || headers.get("authorization");
}
return headers.Authorization || headers.authorization;
};
const retryRequestWithFreshToken = async (requestConfig?: AuthRetryConfig) => {
if (!requestConfig || requestConfig._authRetried) return null;
const hasOldAccessToken = Boolean(
getStoredAccessToken() || getRequestAuthorization(requestConfig),
);
if (!hasOldAccessToken) return null;
requestConfig._authRetried = true;
clearPersistedAuth();
const accessToken = await refreshPersistedAuth();
if (!accessToken) return null;
requestConfig.headers = {
...(requestConfig.headers || {}),
Authorization: `Bearer ${accessToken}`,
};
// 只有旧 accessToken 过期时才尝试刷新并重放一次,未登录 401 直接去 Admin。
return request.request(requestConfig);
};
const redirectAfterAuthExpired = () => {
clearPersistedAuth();
redirectToAdminLogin();
};
request.interceptors.request.use(async (requestConfig) => { request.interceptors.request.use(async (requestConfig) => {
let accessToken = getStoredAccessToken(); let accessToken = getStoredAccessToken();
@ -42,19 +94,25 @@ request.interceptors.request.use(async (requestConfig) => {
}); });
request.interceptors.response.use( request.interceptors.response.use(
(response) => { async (response) => {
if (response.data?.code === 401) { if (response.data?.code === 401) {
clearPersistedAuth(); const retryResponse = await retryRequestWithFreshToken(response.config as AuthRetryConfig);
redirectToAdminLogin(); if (retryResponse) return retryResponse;
return Promise.reject(new Error(response.data?.msg || "登录已过期"));
redirectAfterAuthExpired();
return Promise.reject(new Error(getAuthErrorMessage(response.data)));
} }
return response.data; return response.data;
}, },
(error) => { async (error) => {
if (axios.isAxiosError(error) && error.response?.status === 401) { if (axios.isAxiosError(error) && error.response?.status === 401) {
clearPersistedAuth(); const retryResponse = await retryRequestWithFreshToken(
redirectToAdminLogin(); error.config as AuthRetryConfig | undefined,
);
if (retryResponse) return retryResponse;
redirectAfterAuthExpired();
} }
return Promise.reject(error); return Promise.reject(error);