diff --git a/apps/web-tdesign/src/api/core/auth.ts b/apps/web-tdesign/src/api/core/auth.ts index 6983584..c2c88c9 100644 --- a/apps/web-tdesign/src/api/core/auth.ts +++ b/apps/web-tdesign/src/api/core/auth.ts @@ -7,14 +7,12 @@ export namespace AuthApi { username?: string; } - /** 登录接口返回值 */ + /** 登录/刷新接口返回值(后端 TokenPair) */ export interface LoginResult { accessToken: string; - } - - export interface RefreshTokenResult { - data: string; - status: number; + adminId: number; + expiresIn: number; + refreshToken: string; } } @@ -29,23 +27,23 @@ export async function loginApi(data: AuthApi.LoginParams) { } /** - * 刷新accessToken + * 刷新 accessToken(旋转令牌:旧 refreshToken 单次有效,返回新令牌对) + * @param refreshToken 当前保存的刷新令牌,后端按其 JTI 撤销旧会话 + * @returns raw 模式 axios response:resp.data 为 {code,message,data} 信封 */ -export async function refreshTokenApi() { - return baseRequestClient.post( - '/api/service/admin/system/auth/refresh', - { - withCredentials: true, - }, - ); +export async function refreshTokenApi(refreshToken: string) { + return baseRequestClient.post<{ + data: { code: number; data: AuthApi.LoginResult; message: string }; + status: number; + }>('/api/service/admin/system/auth/refresh', { refreshToken }); } /** - * 退出登录 + * 退出登录:携带 refreshToken 让后端撤销刷新会话(幂等) */ -export async function logoutApi() { +export async function logoutApi(refreshToken?: string) { return baseRequestClient.post('/api/service/admin/system/auth/logout', { - withCredentials: true, + refreshToken, }); } diff --git a/apps/web-tdesign/src/api/request.ts b/apps/web-tdesign/src/api/request.ts index 86b181d..4c00830 100644 --- a/apps/web-tdesign/src/api/request.ts +++ b/apps/web-tdesign/src/api/request.ts @@ -46,13 +46,20 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 刷新token逻辑 + * 后端为旋转令牌:旧 refreshToken 单次有效,响应返回新令牌对。 + * baseRequestClient 为 raw 模式(axios response),body 在 resp.data, + * 信封为 {code,message,data},令牌对在 resp.data.data。 */ async function doRefreshToken() { const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; + const resp = await refreshTokenApi(accessStore.refreshToken ?? ''); + const pair = resp.data?.data; + if (!pair?.accessToken) { + throw new Error('refresh token response invalid'); + } + accessStore.setAccessToken(pair.accessToken); + accessStore.setRefreshToken(pair.refreshToken); + return pair.accessToken; } function formatToken(token: null | string) { diff --git a/apps/web-tdesign/src/store/auth.ts b/apps/web-tdesign/src/store/auth.ts index 7f8d8f9..bef7c71 100644 --- a/apps/web-tdesign/src/store/auth.ts +++ b/apps/web-tdesign/src/store/auth.ts @@ -29,15 +29,17 @@ export const useAuthStore = defineStore('auth', () => { params: Recordable, onSuccess?: () => Promise | void, ) { - // 异步处理用户登录操作并获取 accessToken + // 异步处理用户登录操作并获取令牌对(accessToken + refreshToken) let userInfo: null | UserInfo = null; try { loginLoading.value = true; - const { accessToken } = await loginApi(params); + const { accessToken, refreshToken } = await loginApi(params); // 如果成功获取到 accessToken if (accessToken) { accessStore.setAccessToken(accessToken); + // 刷新令牌持久化(pinia persist),供 access token 过期时静默续期 + accessStore.setRefreshToken(refreshToken); // 获取用户信息并存储到 accessStore 中 const [fetchUserInfoResult, accessCodes] = await Promise.all([ fetchUserInfo(), @@ -78,7 +80,9 @@ export const useAuthStore = defineStore('auth', () => { async function logout(redirect: boolean = true) { try { - await logoutApi(); + // 携带 refreshToken 让后端撤销刷新会话(阻止该设备继续续期); + // 后端幂等,即使令牌无效也返回成功,失败不阻塞本地登出。 + await logoutApi(accessStore.refreshToken ?? undefined); } catch { // 不做任何处理 }