From 1b93c1b0ad8b022a697ee9c819b52e54325c7ab4 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Tue, 26 May 2026 13:02:37 +0800 Subject: [PATCH] =?UTF-8?q?notice(=E5=90=8E=E5=8F=B0=E7=99=BB=E5=BD=95):?= =?UTF-8?q?=20admin=20=E7=99=BB=E5=BD=95=E6=80=81=E7=BA=A62h=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E9=9C=80=E5=89=8D=E7=AB=AF=E6=8E=A5=E5=85=A5refresh?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E7=BB=AD=E6=9C=9F(=E6=A0=B9=E5=9B=A0?= =?UTF-8?q?=E6=8E=92=E6=9F=A5=E7=BB=93=E8=AE=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...min_login_token_expire_frontend_refresh.md | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 changelogs/2026-05/26_notice_admin_login_token_expire_frontend_refresh.md diff --git a/changelogs/2026-05/26_notice_admin_login_token_expire_frontend_refresh.md b/changelogs/2026-05/26_notice_admin_login_token_expire_frontend_refresh.md new file mode 100644 index 0000000..32a9d48 --- /dev/null +++ b/changelogs/2026-05/26_notice_admin_login_token_expire_frontend_refresh.md @@ -0,0 +1,82 @@ +# notice(后台登录): admin 登录态约 2 小时就过期 → 需前端接入 refresh 静默续期(根因排查结论) + +> **类型**: notice(后端无代码改动;双 token 机制后端早已就绪,缺前端接入) +> **关联 PR**: 无(后端不改);双 token 机制为工单 #2309 既有能力 +> **日期**: 2026-05-26 +> **影响接口**: `POST /admin/auth/login`(登录,已返回 refreshToken) + `POST /admin/auth/refresh`(刷新) +> **接收方**: mmg +> **前端**: **必须接入**(否则管理员每约 2 小时必被强制弹「登录已过期」一次) + +--- + +## 🎯 背景 / 现象 + +运营反馈正式后台管理端「登录状态老短时间就过期」,弹窗提示「登录已过期 / invalid token」,重新登录后用一会儿又掉。 + +经后端彻底排查(正式环境实测),结论如下: + +- admin 的 **access token 固定 2 小时过期**(绝对时间,不随操作滑动续期)。正式 nacos 未单独配置过期时长,走后端默认 7200 秒。 +- 后端**双 token 机制完整且正常**:登录已返回 `refreshToken`(有效期 30 天),`/admin/auth/refresh` 接口实测可正常用 refreshToken 换发新 access。 +- JWT 签名/验签、网关校验**均正常**,排除密钥/配置问题。 +- **唯一缺口在前端**:hl-ui 全量检索 `refreshToken` / `auth/refresh` **零命中** —— 前端登录后**没有保存 refreshToken,也没有任何静默续期逻辑**。access token 一过 2 小时,前端收到 401 就直接弹「登录已过期」让用户重新登录。 + +即:双 token 续期闭环的「后端发券」已就绪,「前端用券续期」这一段没做,导致 access 一过期只能重新登录。 + +--- + +## 📋 后端接口契约(已就绪,可直接接) + +### 1. 登录 `POST /admin/auth/login`(已存在,无变化) + +响应 `data` 字段(`AdminLoginResponse`): + +| 字段 | 类型 | 说明 | +|------|------|------| +| `token` | string | access token,有效期见 `accessTokenExpiresIn` | +| `refreshToken` | string | **刷新令牌,前端必须保存**(此前可能被前端忽略丢弃) | +| `accessTokenExpiresIn` | number | access 有效期**秒数**,当前 = `7200`(2 小时) | +| `refreshTokenExpiresIn` | number | refresh 有效期**秒数**,当前 = `2592000`(30 天) | +| `adminId` / `username` / `role` / `roleName` / `roles` / `avatar` / `mobile` 等 | - | 用户信息(不变) | + +### 2. 刷新 `POST /admin/auth/refresh`(换发新 access) + +- **无需 Authorization 头**(网关已放行该路径) +- 请求体: + +```json +{ "refreshToken": "登录时保存的 refreshToken" } +``` + +- 响应 `data` 结构**与登录响应完全一致**(同样是 `AdminLoginResponse`),其中: + - `token`:**新的 access token**(前端用它替换旧 access) + - `refreshToken`:**新的 refresh token —— 会轮换!与旧的不同,前端必须一并更新保存**(实测每次 refresh 都会返回新串,旧串随后失效) + - `accessTokenExpiresIn` / `refreshTokenExpiresIn`:同上 + +- 失败(refreshToken 过期 / 无效 / 已被新登录覆盖)时返回非 200 业务码,此时才真正需要跳登录页。 + +--- + +## ⚠️ 前端需要做的事 + +1. **保存 refreshToken**:登录成功后,把 `data.refreshToken` 与 access token 一起存起来(同 access 的存储介质)。 +2. **静默续期**(二选一或叠加): + - 方案 A(推荐,无感):根据 `accessTokenExpiresIn` 设定时器,在 access 临近过期(如剩余 5 分钟)时,后台静默调 `/admin/auth/refresh` 换新,**不打扰用户**。 + - 方案 B(兜底):在 axios 响应拦截器中,捕获 access 过期的 401(`Invalid token`),**先尝试用 refreshToken 调 `/admin/auth/refresh`**,成功则用新 access **重放原失败请求**;只有 refresh 也失败时,才走现有的「登录已过期」弹窗 + 跳登录页逻辑(`src/utils/request.js` 现有 401 分支)。 +3. **每次 refresh 后更新存储**:用返回的新 `token` 和新 `refreshToken` 覆盖旧值(refreshToken 会轮换,务必更新,否则下次续期会拿到失效的旧串)。 +4. **并发请求处理**:多个请求同时 401 时,只发起一次 refresh,其余请求挂起等新 token 后重放(避免重复刷新)。现有代码已有 `isHandling401` 单飞标志,可复用同样思路做 refresh 单飞。 +5. **登出 / refresh 失败**:清空 access + refreshToken,跳登录页(沿用现有逻辑)。 + +--- + +## 🔧 后端状态 + +- 后端**无需任何改动**,`/admin/auth/refresh` 与登录返回 refreshToken 均已在正式环境就绪并实测通过。 +- 如前端短期内无法接入,后端可作为**临时缓解**调大 access 有效期(改正式 nacos `admin-access-ttl-seconds` 并重启),但这会拉长 token 泄露窗口,**非长久之计**,正解仍是前端接入 refresh 续期。是否启用此临时方案由后端按需评估。 + +--- + +## ✅ 验证方式(前端接入后) + +1. 登录后等 access 过期(可临时把判定阈值调短便于测试),期间持续操作,应**全程无「登录已过期」弹窗**,网络面板可见自动发起的 `/admin/auth/refresh`。 +2. 手动篡改本地 access 为过期串发请求,应触发静默 refresh + 原请求重放成功。 +3. 把本地 refreshToken 也置为无效,再触发,应正常落到「登录已过期」弹窗 + 跳登录页。