ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

vue-vben-admin 5.8.0 修复解读:Ant Design Vue Next 变体的 refresh/logout 请求 withCredentials 处理

2026/9/10 13:16:08 拓冰建站 浏览量
vue-vben-admin 5.8.0 修复解读:Ant Design Vue Next 变体的 refresh/logout 请求 withCredentials 处理 vue-vben-admin 5.8.0 修复解读Ant Design Vue Next 变体的 refresh/logout 请求 withCredentials 处理【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin本文基于apps/web-antdv-next的 5.8.0 版本 CHANGELOG 记录深入解读该版本中一项关键的认证修复——在refresh刷新 accessToken与logout退出登录两个请求上显式携带withCredentials配置并结合仓库源码与后端 Mock 实现剖析其背后的 httpOnly Cookie 刷新令牌机制、请求拦截器刷新流程与验证方式。一、这次 5.8.0 修复了什么在 CHANGELOG 的5.8.0版本下有一条 Patch Changes 记录PR #8308fix(apps): pass withCredentials as request config for refresh/logout修复者kilisamemarisaaa将withCredentials作为请求配置传入 refresh 与 logout 两个接口。这虽然只是一行配置的变更但它修复的是一个在**跨域 携带 Cookie 的刷新令牌Refresh Token**场景下极易出现、且难以排查的认证失效问题。要理解这次修复的价值需要先回答三个问题withCredentials是什么、为什么刷新令牌必须依赖它为什么login能正常工作而refresh/logout却可能拿不到 Cookie修复之后完整的令牌刷新链路是如何串起来的。下文将逐层展开并用仓库源码作为证据。二、withCredentials让跨域请求带上 Cookie浏览器对跨域请求默认不会携带 Cookie。XMLHttpRequest/fetch的withCredentials选项用于指示浏览器在跨域请求中是否携带目标域名的 Cookie 与 HTTP 认证信息。在 vue-vben-admin 中前端应用与后端接口往往部署在不同的源例如前端localhost:5173、Mock 后端localhost:5320参见各应用vite.config.ts中的代理配置因此只要涉及通过 Cookie 传递凭证的接口就必须显式开启withCredentials: true否则请求到达后端时 Cookie 为空后端会认为请求未携带刷新令牌而直接拒绝。而 vue-vben-admin 恰好把refreshToken 存放在 httpOnly Cookie中而不是像 accessToken 那样放在请求头Authorization: Bearer token里这正是本次修复的技术背景详见下文第三节。修复点对应的源码本次修复落实到web-antdv-next的认证 API 定义中见 apps/web-antdv-next/src/api/core/auth.ts/** * 刷新accessToken */ export async function refreshTokenApi() { return baseRequestClient.postAuthApi.RefreshTokenResult( /auth/refresh, undefined, { withCredentials: true, }, ); } /** * 退出登录 */ export async function logoutApi() { return baseRequestClient.post(/auth/logout, undefined, { withCredentials: true, }); }从代码可以看到refreshTokenApi使用baseRequestClient不携带 Authorization 头的裸请求客户端调用POST /auth/refresh并把withCredentials: true作为第三参数请求配置传入logoutApi同样以withCredentials: true调用POST /auth/logout而loginApi走的是requestClient登录成功后由后端通过Set-Cookie种下刷新令牌因此登录请求本身不依赖已有 Cookie。baseRequestClient的定义位于 apps/web-antdv-next/src/api/request.tsexport const requestClient createRequestClient(apiURL, { responseReturn: data, }); export const baseRequestClient new RequestClient({ baseURL: apiURL });三、为什么要靠 Cookie 传递刷新令牌Mock 后端的凭证设计在web-antdv-next的 Mock 后端Nitro 应用中刷新令牌的生命周期完全由 Cookie 驱动。核心实现在 apps/backend-mock/utils/cookie-utils.tsexport function clearRefreshTokenCookie(event: H3EventEventHandlerRequest) { deleteCookie(event, jwt, { httpOnly: true, sameSite: none, secure: true, }); } export function setRefreshTokenCookie( event: H3EventEventHandlerRequest, refreshToken: string, ) { setCookie(event, jwt, refreshToken, { httpOnly: true, maxAge: 24 * 60 * 60, // unit: seconds sameSite: none, secure: true, }); } export function getRefreshTokenFromCookie(event: H3EventEventHandlerRequest) { const refreshToken getCookie(event, jwt); return refreshToken; }该 Cookie 的关键属性值得逐条对照Cookie 属性取值作用httpOnlytrue脚本无法通过document.cookie读取降低 XSS 窃取刷新令牌的风险sameSitenone允许跨站携带 Cookie配合跨域部署securetrue仅允许 HTTPS 传输本地开发环境需注意maxAge24 * 60 * 60秒1 天刷新令牌 Cookie 的存活时长刷新令牌本身是 JWT由 apps/backend-mock/utils/jwt-utils.ts 生成与校验const ACCESS_TOKEN_SECRET access_token_secret; const REFRESH_TOKEN_SECRET refresh_token_secret; export function generateAccessToken(user: UserInfo) { return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: 7d }); } export function generateRefreshToken(user: UserInfo) { return jwt.sign(user, REFRESH_TOKEN_SECRET, { expiresIn: 30d, }); }可以看到默认策略是accessToken 有效期 7 天、走请求头refreshToken 有效期 30 天、走 httpOnly Cookie。accessToken 过期后前端用 refreshToken 换取新的 accessToken无需用户重新输入密码。四、登录 → 刷新 → 退出的完整链路1. 登录种下刷新令牌 Cookie登录接口 apps/backend-mock/api/auth/login.post.ts 在校验用户名密码通过后同时生成 accessToken 与 refreshTokenconst accessToken generateAccessToken(findUser); const refreshToken generateRefreshToken(findUser); setRefreshTokenCookie(event, refreshToken); return useResponseSuccess({ ...findUser, accessToken, });即accessToken 通过响应体返回给前端随后被存入 access storerefreshToken 通过Set-Cookie写入浏览器的 httpOnly Cookie。2. 刷新读取 Cookie 并签发新 accessToken刷新接口 apps/backend-mock/api/auth/refresh.post.ts 的处理逻辑export default defineEventHandler(async (event) { const refreshToken getRefreshTokenFromCookie(event); if (!refreshToken) { return forbiddenResponse(event); } clearRefreshTokenCookie(event); const userinfo verifyRefreshToken(refreshToken); if (!userinfo) { return forbiddenResponse(event); } const findUser MOCK_USERS.find( (item) item.username userinfo.username, ); if (!findUser) { return forbiddenResponse(event); } const accessToken generateAccessToken(findUser); setRefreshTokenCookie(event, refreshToken); return accessToken; });这段代码清晰展示了 Cookie 刷新机制的两个关键点读取凭证的唯一途径就是请求携带的jwtCookiegetRefreshTokenFromCookie。如果前端请求没有withCredentials: true跨域场景下 Cookie 不会被携带接口直接返回 403——这正是本次修复要解决的场景旋转式刷新Refresh Token Rotation每次刷新都会先clearRefreshTokenCookie验证成功后重新setRefreshTokenCookie即刷新令牌的 Cookie 被重新种下Mock 实现中复用了原 refreshToken生产环境可签发新令牌并作废旧令牌。3. 退出清除 Cookie退出接口 apps/backend-mock/api/auth/logout.post.ts 同样依赖 Cookieexport default defineEventHandler(async (event) { const refreshToken getRefreshTokenFromCookie(event); if (!refreshToken) { return useResponseSuccess(); } clearRefreshTokenCookie(event); return useResponseSuccess(); });如果logout请求没有携带withCredentials: true服务端读取不到jwtCookie虽然接口仍返回成功但服务端无法真正清除服务端视角下的会话状态而前端清除的只是内存中的 accessToken刷新令牌 Cookie 仍残留在浏览器中造成退出后刷新令牌依然有效的隐患。修复后浏览器会随请求携带 Cookie服务端得以可靠地清除它。五、前端拦截器如何串起整条刷新链路refreshTokenApi/logoutApi被谁调用答案在 apps/web-antdv-next/src/api/request.ts 中定义的两个拦截器中。1. 请求拦截器为每个请求注入 Authorization 头client.addRequestInterceptor({ fulfilled: async (config) { const accessStore useAccessStore(); config.headers.Authorization formatToken(accessStore.accessToken); config.headers[Accept-Language] preferences.app.locale; return config; }, });从 access store 中读取当前 accessToken拼成Bearer token写入Authorization头并附带当前语言偏好。2. 响应拦截器401 时自动刷新并重放请求authenticateResponseInterceptor位于 packages/effects/request/src/request-client/preset-interceptors.ts其核心逻辑rejected: async (error) { const { config, response } error; // 如果不是 401 错误直接抛出异常 if (response?.status ! 401) { throw error; } // 判断是否启用了 refreshToken 功能 // 如果没有启用或者已经是重试请求了直接跳转到重新登录 if (!enableRefreshToken || config.__isRetryRequest) { await doReAuthenticate(); throw error; } // 如果正在刷新 token则将请求加入队列等待刷新完成 if (client.isRefreshing) { return new Promise((resolve) { client.refreshTokenQueue.push((newToken: string) { config.headers.Authorization formatToken(newToken); resolve(client.request(config.url, { ...config })); }); }); } // 标记开始刷新 token client.isRefreshing true; // 标记当前请求为重试请求避免无限循环 config.__isRetryRequest true; try { const newToken await doRefreshToken(); // 处理队列中的请求 client.refreshTokenQueue.forEach((callback) callback(newToken)); // 清空队列 client.refreshTokenQueue []; return client.request(error.config.url, { ...error.config }); } catch (refreshError) { // 如果刷新 token 失败处理错误如强制登出或跳转登录页面 client.refreshTokenQueue.forEach((callback) callback()); client.refreshTokenQueue []; console.error(Refresh token failed, please login again.); await doReAuthenticate(); throw refreshError; } finally { client.isRefreshing false; } },而doRefreshToken正是调用本次修复的refreshTokenApiasync function doRefreshToken() { const accessStore useAccessStore(); const resp await refreshTokenApi(); const newToken resp.data; accessStore.setAccessToken(newToken); return newToken; }刷新失败时的兜底doReAuthenticate则会清空 accessToken并根据preferences.app.loginExpiredMode决定是弹出登录过期弹窗还是直接登出async function doReAuthenticate() { console.warn(Access token or refresh token is invalid or expired. ); const accessStore useAccessStore(); const authStore useAuthStore(); accessStore.setAccessToken(null); if ( preferences.app.loginExpiredMode modal accessStore.isAccessChecked ) { accessStore.setLoginExpired(true); } else { await authStore.logout(); } }刷新失败时的报错文案与状态码映射同一文件中的errorMessageResponseInterceptor负责把网络错误与 HTTP 状态码翻译成用户可读的提示其中 401 对应未登录或登录已过期case 401: { errorMessage $t(ui.fallback.http.unauthorized); break; }六、修复的验证方式修复是否生效可以通过以下方式在本地验证启动应用与 Mock 后端在web-antdv-next目录执行pnpm dev对应 package.json 中的dev: pnpm vite --mode development同时启动apps/backend-mock。登录调用loginApi登录成功后在浏览器开发者工具中确认响应Set-Cookie: jwt...且该 Cookie 属性包含HttpOnly、SameSiteNone、Secure。观察刷新请求将 accessToken 改短使其过期或直接构造一个 401 响应触发authenticateResponseInterceptor在 Network 面板检查POST /auth/refresh请求的Request Headers 中是否携带了Cookie: jwt...。修复前该请求不带 Cookie、返回 403修复后携带 Cookie 并返回新的 accessToken随后原请求被自动重放。观察退出请求点击退出登录检查POST /auth/logout请求同样携带Cookie响应后服务端清除jwtCookie。如果部署在跨域环境还需确保后端Access-Control-Allow-Credentials: true且Access-Control-Allow-Origin不是*必须为具体源否则即使前端开启withCredentials浏览器也会拦截带凭证的跨域响应。七、5.8.0 版本中该应用的其他同步更新本次修复属于vben/web-antdv-next应用的补丁变更随同版本还同步升级了一批底层依赖包均为5.8.0版本它们在 CHANGELOG 中列出并在 package.json 中以workspace:*的形式引用依赖包说明vben/styles全局样式与主题样式含antdv-next适配样式vben/plugins应用插件集合vben/preferences全局偏好配置含enableRefreshToken、loginExpiredMode等开关vben/layouts布局系统vben/common-ui通用 UI 组件vben/access权限控制vben/hooks组合式函数vben/constants常量定义vben/request请求客户端与拦截器本次修复依赖的RequestClient即来自此包vben/icons图标vben/locales国际化vben/storesPinia store 集合vben/types类型定义vben/utils工具函数UI 层使用antdv-nextAnt Design Vue Next组件库因此该应用的启动入口 bootstrap.ts 会引入vben/styles/antdv-next样式并通过 adapter/component/index.ts 完成组件适配器的初始化。八、小结web-antdv-next5.8.0 的这则补丁变更虽小却是httpOnly Cookie 刷新令牌安全模型能否在跨域环境下闭环的关键一环修复前refresh/logout请求不带withCredentials浏览器不携带jwtCookie刷新令牌失效、退出无法清理服务端会话修复后两个接口显式携带凭证配合请求拦截器实现 401 自动刷新、请求重放与失败兜底登出形成完整的令牌生命周期管理。如果你正在基于 vue-vben-admin 的任一应用变体web-antd、web-antdv-next、web-ele、web-naive、web-tdesign、playground进行二次开发且后端将刷新令牌放入 httpOnly Cookie请务必检查自己的refreshTokenApi/logoutApi是否同样携带了withCredentials: true——这是极易被遗漏、又直接影响登录态稳定性的细节。【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考