240 lines
85 KiB
JavaScript
240 lines
85 KiB
JavaScript
import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.BbmSSMjL.js";const c=JSON.parse('{"title":"8.6 Token 使用说明","description":"","frontmatter":{},"headers":[],"relativePath":"zh/api-spec/token.md","filePath":"zh/api-spec/token.md"}'),h={name:"zh/api-spec/token.md"};function p(e,s,l,k,d,r){return a(),n("div",null,[...s[0]||(s[0]=[t(`<h1 id="_8-6-Token-使用说明" tabindex="-1">8.6 Token 使用说明 <a class="header-anchor" href="#_8-6-Token-使用说明" aria-label="Permalink to "8.6 Token 使用说明""></a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>系统采用 JWT 双令牌认证,access_token 用于接口认证(2 小时),refresh_token 用于刷新令牌(7 天)。refresh_token 存储于 Redis(jti 有状态),支持<strong>一次性刷新、登出吊销、踢下线</strong>。所有需认证的接口必须在请求头中携带 access_token。</p></div><h2 id="Token-完整生命周期" tabindex="-1">Token 完整生命周期 <a class="header-anchor" href="#Token-完整生命周期" aria-label="Permalink to "Token 完整生命周期""></a></h2><h3 id="登录获取-Token" tabindex="-1">登录获取 Token <a class="header-anchor" href="#登录获取-Token" aria-label="Permalink to "登录获取 Token""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>用户输入账号密码</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 前端:POST /api/login │</span></span>
|
||
<span class="line"><span>│ { username, password, code, key } │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 后端:AuthController → JwtService │</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ 1. LoginRequest 参数格式校验 │</span></span>
|
||
<span class="line"><span>│ 2. CaptchaService::verify() 验证码校验(key/code) │</span></span>
|
||
<span class="line"><span>│ 3. User::where('username') 查询用户 │</span></span>
|
||
<span class="line"><span>│ 4. PasswordService::verify() 密码校验(双重加盐) │</span></span>
|
||
<span class="line"><span>│ 5. createTokenPair() 签发双令牌 + refresh 存 Redis │</span></span>
|
||
<span class="line"><span>│ (同一 jti,Redis 键 refresh:{uid}:{jti},TTL 7 天) │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 响应(登录/刷新接口 raw 返回,保持 snake_case): │</span></span>
|
||
<span class="line"><span>│ { │</span></span>
|
||
<span class="line"><span>│ "access_token": "eyJ...", ← 用于接口认证 │</span></span>
|
||
<span class="line"><span>│ "refresh_token": "eyJ...", ← 用于刷新令牌 │</span></span>
|
||
<span class="line"><span>│ "token_type": "Bearer", │</span></span>
|
||
<span class="line"><span>│ "expires_in": 7200, ← 2小时 = 7200秒 │</span></span>
|
||
<span class="line"><span>│ "user": { "id":1, "username":"admin", ... } │</span></span>
|
||
<span class="line"><span>│ } │</span></span>
|
||
<span class="line"><span>│ (AuthController 调用 success(..., true) 跳过驼峰转换, │</span></span>
|
||
<span class="line"><span>│ 与前端 store 的 data.access_token 读取方式保持一致) │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 前端:存储 Token(仅存 access_token) │</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ storage.set('ACCESS-TOKEN', data.access_token) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h3 id="使用-Token-请求-API" tabindex="-1">使用 Token 请求 API <a class="header-anchor" href="#使用-Token-请求-API" aria-label="Permalink to "使用 Token 请求 API""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>用户操作(如点击"用户列表")</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 前端:请求拦截器 │</span></span>
|
||
<span class="line"><span>│ const token = storage.get('ACCESS-TOKEN') │</span></span>
|
||
<span class="line"><span>│ config.headers['Authorization'] = \`Bearer \${token}\` │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ GET /api/user/page?pageNo=1&pageSize=10 │</span></span>
|
||
<span class="line"><span>│ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 后端:Authenticate 中间件(auth.jwt) │</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ 1. 提取 Authorization 头(带 Bearer 前缀则剥离,裸 token 亦可)│</span></span>
|
||
<span class="line"><span>│ │ 头缺失 → HTTP 200 "未提供认证令牌" │</span></span>
|
||
<span class="line"><span>│ 2. JwtService::parseToken()(校验签名+有效期+type=access) │</span></span>
|
||
<span class="line"><span>│ │ 过期/签名错/类型错 → HTTP 200 │</span></span>
|
||
<span class="line"><span>│ 3. 注入 attributes['userInfo'] │</span></span>
|
||
<span class="line"><span>│ 4. 反射 #[Permission] → checkPermission() │</span></span>
|
||
<span class="line"><span>│ │ 无权限 → HTTP 403 "无访问权限:{权限码}" │</span></span>
|
||
<span class="line"><span>│ 5. 放行 → Controller → Service → Model → 响应 │</span></span>
|
||
<span class="line"><span>└─────────────────────────┬───────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 前端:响应拦截器 │</span></span>
|
||
<span class="line"><span>│ code === 0 → 返回 data(成功) │</span></span>
|
||
<span class="line"><span>│ code/status 401 → 弹窗"登录身份已失效" → 清存储 → 跳登录页 │</span></span>
|
||
<span class="line"><span>│ code/status 403 → 提示"无访问权限" │</span></span>
|
||
<span class="line"><span>│ 其他 → 提示 msg │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h3 id="Token-刷新-后端能力-" tabindex="-1">Token 刷新(后端能力) <a class="header-anchor" href="#Token-刷新-后端能力-" aria-label="Permalink to "Token 刷新(后端能力)""></a></h3><p>access_token 过期后,客户端可用 refresh_token 换取新令牌对:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>POST /api/refresh</span></span>
|
||
<span class="line"><span>{ "refresh_token": "eyJ..." } ← 主入口(公开接口)</span></span>
|
||
<span class="line"><span> (或 POST /api/oauth2/token,grant_type=refresh_token)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>JwtService::refreshToken()</span></span>
|
||
<span class="line"><span> │ 1. 解析 refresh_token → 失败 → "无效的refresh_token"</span></span>
|
||
<span class="line"><span> │ 2. type 必须为 refresh</span></span>
|
||
<span class="line"><span> │ 3. Lua 原子校验并删除 Redis refresh:{uid}:{jti}(一次性)</span></span>
|
||
<span class="line"><span> │ 已用过/已吊销 → "refresh_token已失效"</span></span>
|
||
<span class="line"><span> │ 4. 用户状态校验 → "用户不存在或已被禁用"</span></span>
|
||
<span class="line"><span> │ 5. createTokenPair() → 新 jti 签发新令牌对</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>返回新令牌对(access_token / refresh_token 均为新值)</span></span></code></pre></div><div class="warning custom-block"><p class="custom-block-title">一次性刷新(防重放)</p><p>旧 refresh_token 刷新后立即从 Redis 删除——<strong>必须保存返回的新 refresh_token</strong>,重放旧令牌会得到 <code>refresh_token已失效</code>。</p></div><div class="info custom-block"><p class="custom-block-title">管理后台前端的现状</p><p><code>ui/</code> 管理后台当前<strong>未内置自动续签</strong>:access_token 过期(401)时直接弹窗提示重新登录(见下文「响应拦截器」)。<code>POST /api/refresh</code> 面向第三方接入客户端与后续前端升级使用。</p></div><h3 id="Token-失效-重新登录-" tabindex="-1">Token 失效(重新登录) <a class="header-anchor" href="#Token-失效-重新登录-" aria-label="Permalink to "Token 失效(重新登录)""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>access_token 过期 / refresh_token 刷新失败(过期/已吊销/网络异常)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 前端:清除登录态,跳转登录页 │</span></span>
|
||
<span class="line"><span>│ storage.clear() │</span></span>
|
||
<span class="line"><span>│ window.location.href = '/login' │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h3 id="两种-Token-的区别" tabindex="-1">两种 Token 的区别 <a class="header-anchor" href="#两种-Token-的区别" aria-label="Permalink to "两种 Token 的区别""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ access_token │</span></span>
|
||
<span class="line"><span>│ 用途:接口认证(每个 API 请求携带) │</span></span>
|
||
<span class="line"><span>│ 有效期:2 小时(JWT_ACCESS_TTL=7200 秒) │</span></span>
|
||
<span class="line"><span>│ payload:type=access,含 jti │</span></span>
|
||
<span class="line"><span>│ 传递方式:Authorization: Bearer <token> │</span></span>
|
||
<span class="line"><span>│ 校验方:Authenticate 中间件 │</span></span>
|
||
<span class="line"><span>│ 特点:短期有效、无状态,泄露影响范围有限 │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ refresh_token │</span></span>
|
||
<span class="line"><span>│ 用途:刷新令牌(仅在 access_token 过期时使用) │</span></span>
|
||
<span class="line"><span>│ 有效期:7 天(JWT_REFRESH_TTL=604800 秒) │</span></span>
|
||
<span class="line"><span>│ payload:type=refresh,含与 access 相同的 jti │</span></span>
|
||
<span class="line"><span>│ 传递方式:POST /api/refresh 请求体 │</span></span>
|
||
<span class="line"><span>│ 校验方:JwtService::refreshToken()(Redis jti 一次性校验) │</span></span>
|
||
<span class="line"><span>│ 特点:有状态(Redis)、可吊销、支持踢下线 │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h2 id="请求头格式" tabindex="-1">请求头格式 <a class="header-anchor" href="#请求头格式" aria-label="Permalink to "请求头格式""></a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>Authorization: Bearer <access_token></span></span></code></pre></div><h3 id="示例" tabindex="-1">示例 <a class="header-anchor" href="#示例" aria-label="Permalink to "示例""></a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># curl</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">curl</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> http://admin.laravel13.naivevue/api/user/page</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
|
||
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -H</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># curl(带分页参数)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">curl</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">http://admin.laravel13.naivevue/api/user/page?pageNo=1&pageSize=10</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
|
||
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -H</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span></code></pre></div><h3 id="请求头配置" tabindex="-1">请求头配置 <a class="header-anchor" href="#请求头配置" aria-label="Permalink to "请求头配置""></a></h3><table tabindex="0"><thead><tr><th>配置项</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>jwt.header_prefix</code></td><td><code>Bearer </code></td><td>请求头前缀(注意有尾部空格,<code>config/jwt.php</code>)</td></tr></tbody></table><h2 id="Token-获取" tabindex="-1">Token 获取 <a class="header-anchor" href="#Token-获取" aria-label="Permalink to "Token 获取""></a></h2><h3 id="登录获取-主入口-" tabindex="-1">登录获取(主入口) <a class="header-anchor" href="#登录获取-主入口-" aria-label="Permalink to "登录获取(主入口)""></a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">POST</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/login</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Content-Type:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> application/json</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> "username"</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> "password"</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">123456</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> "code"</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">a3Bx</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> "key"</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">captcha_key_xxx</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p><strong>成功响应:</strong></p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">登录成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">access_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">refresh_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">token_type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Bearer</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">expires_in</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 7200</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">user</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">id</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">realname</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">管理员</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">avatar</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">user/avatar/20260923/100429_e8FAKf9r.png</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="OAuth2-密码模式获取" tabindex="-1">OAuth2 密码模式获取 <a class="header-anchor" href="#OAuth2-密码模式获取" aria-label="Permalink to "OAuth2 密码模式获取""></a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">POST</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/oauth2/token</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Content-Type:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> application/x-www-form-urlencoded</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">grant_type</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">password</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">&</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">username</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">&</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">password</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">123456</span></span></code></pre></div><h3 id="响应字段" tabindex="-1">响应字段 <a class="header-anchor" href="#响应字段" aria-label="Permalink to "响应字段""></a></h3><table tabindex="0"><thead><tr><th>字段</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>access_token</code></td><td>string</td><td>访问令牌(用于接口认证)</td></tr><tr><td><code>refresh_token</code></td><td>string</td><td>刷新令牌(用于换取新令牌)</td></tr><tr><td><code>token_type</code></td><td>string</td><td>令牌类型(固定 <code>Bearer</code>)</td></tr><tr><td><code>expires_in</code></td><td>number</td><td>access_token 有效期(秒,默认 7200)</td></tr><tr><td><code>user</code></td><td>object</td><td>用户基础信息(id、username、realname、avatar)</td></tr></tbody></table><div class="tip custom-block"><p class="custom-block-title">登录/刷新接口为 snake_case 特例</p><p>一般接口响应经驼峰自动转换(<code>create_time</code> → <code>createTime</code>);登录、刷新等令牌接口由 <code>AuthController</code> 以 <code>success(..., true)</code> raw 返回,键名保持 snake_case,与前端 <code>data.access_token</code> 读取方式一致。</p></div><h2 id="Token-刷新" tabindex="-1">Token 刷新 <a class="header-anchor" href="#Token-刷新" aria-label="Permalink to "Token 刷新""></a></h2><p>access_token 过期后(code=401),使用 refresh_token 换取新令牌对:</p><h3 id="接口信息" tabindex="-1">接口信息 <a class="header-anchor" href="#接口信息" aria-label="Permalink to "接口信息""></a></h3><table tabindex="0"><thead><tr><th>项目</th><th>说明</th></tr></thead><tbody><tr><td>URL(主入口)</td><td><code>POST /api/refresh</code>(公开接口)</td></tr><tr><td>URL(OAuth2 兼容)</td><td><code>POST /api/oauth2/token</code>(grant_type=refresh_token)</td></tr><tr><td>Content-Type</td><td><code>application/json</code> 或 <code>application/x-www-form-urlencoded</code></td></tr></tbody></table><h3 id="请求示例" tabindex="-1">请求示例 <a class="header-anchor" href="#请求示例" aria-label="Permalink to "请求示例""></a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 推荐:/api/refresh(JSON)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">POST</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/refresh</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Content-Type:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> application/json</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> "refresh_token"</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># OAuth2 兼容(表单)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">POST</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/oauth2/token</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Content-Type:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> application/x-www-form-urlencoded</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">grant_type</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">refresh_token</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">&</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">refresh_token</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOi...</span></span></code></pre></div><h3 id="成功响应" tabindex="-1">成功响应 <a class="header-anchor" href="#成功响应" aria-label="Permalink to "成功响应""></a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">刷新成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">access_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ...(新access_token)</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">refresh_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ...(新refresh_token)</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">token_type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Bearer</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">expires_in</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 7200</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="失败响应" tabindex="-1">失败响应 <a class="header-anchor" href="#失败响应" aria-label="Permalink to "失败响应""></a></h3><table tabindex="0"><thead><tr><th>场景</th><th>code</th><th>msg</th></tr></thead><tbody><tr><td>缺少 refresh_token</td><td>1</td><td>缺少refresh_token</td></tr><tr><td>refresh_token 无效/类型错</td><td>1</td><td>无效的refresh_token</td></tr><tr><td>旧令牌已用过(重放)/ 已吊销</td><td>1</td><td>refresh_token已失效</td></tr><tr><td>用户不存在/禁用</td><td>1</td><td>用户不存在或已被禁用</td></tr></tbody></table><h2 id="Token-有效性检查" tabindex="-1">Token 有效性检查 <a class="header-anchor" href="#Token-有效性检查" aria-label="Permalink to "Token 有效性检查""></a></h2><h3 id="接口信息-1" tabindex="-1">接口信息 <a class="header-anchor" href="#接口信息-1" aria-label="Permalink to "接口信息""></a></h3><table tabindex="0"><thead><tr><th>项目</th><th>说明</th></tr></thead><tbody><tr><td>URL</td><td><code>GET /api/oauth2/check/token</code></td></tr><tr><td>权限</td><td>无需认证(公开接口)</td></tr></tbody></table><h3 id="请求示例-1" tabindex="-1">请求示例 <a class="header-anchor" href="#请求示例-1" aria-label="Permalink to "请求示例""></a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 通过请求头传递</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">GET</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/oauth2/check/token</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Authorization:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Bearer</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> <</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">access_toke</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">n</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 通过 URL 参数传递</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">GET</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/oauth2/check/token?token=</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"><</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">access_toke</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">n</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">></span></span></code></pre></div><h3 id="成功响应-有效-" tabindex="-1">成功响应(有效) <a class="header-anchor" href="#成功响应-有效-" aria-label="Permalink to "成功响应(有效)""></a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">操作成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">valid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1727007200</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="失败响应-无效-" tabindex="-1">失败响应(无效) <a class="header-anchor" href="#失败响应-无效-" aria-label="Permalink to "失败响应(无效)""></a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">操作成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">valid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> false</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Expired token</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h2 id="Token-吊销" tabindex="-1">Token 吊销 <a class="header-anchor" href="#Token-吊销" aria-label="Permalink to "Token 吊销""></a></h2><table tabindex="0"><thead><tr><th>接口</th><th>方法</th><th>认证</th><th>说明</th></tr></thead><tbody><tr><td><code>/api/logout</code></td><td>POST</td><td>认证组</td><td>登出:吊销当前 refresh_token(<code>revokeToken</code> 删除 Redis jti)</td></tr><tr><td><code>/api/oauth2/remove/{token}</code></td><td>DELETE</td><td>认证组</td><td>吊销指定令牌</td></tr></tbody></table><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 登出(携带 access_token)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">POST</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> /api/logout</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">Authorization:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Bearer</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> <</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">access_toke</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">n</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">></span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">真实吊销(Redis 有状态)</p><p>本项目 refresh_token 以 <code>refresh:{uid}:{jti}</code> 存于 Redis——登出即删除该键,旧 access_token 到期后<strong>无法续签</strong>;<code>JwtService::revokeAllTokens($userId)</code> 可一次性吊销用户全部令牌(踢下线、改密全端失效场景)。</p></div><h2 id="URL-参数传递-SSO-场景-" tabindex="-1">URL 参数传递(SSO 场景) <a class="header-anchor" href="#URL-参数传递-SSO-场景-" aria-label="Permalink to "URL 参数传递(SSO 场景)""></a></h2><p>前端路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>http://localhost:8001/?token=<access_token></span></span></code></pre></div><p><strong>前端处理逻辑:</strong></p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>1. 路由守卫检测 URL 中的 token 参数</span></span>
|
||
<span class="line"><span>2. 将 token 存入 localStorage</span></span>
|
||
<span class="line"><span>3. 清除 URL 中的 token 参数(防止泄露)</span></span>
|
||
<span class="line"><span>4. 后续请求自动携带该 token</span></span></code></pre></div><h2 id="前端存储与使用" tabindex="-1">前端存储与使用 <a class="header-anchor" href="#前端存储与使用" aria-label="Permalink to "前端存储与使用""></a></h2><h3 id="存储-摘自-ui-src-store-modules-user-ts-实际代码-" tabindex="-1">存储(摘自 <code>ui/src/store/modules/user.ts</code> 实际代码) <a class="header-anchor" href="#存储-摘自-ui-src-store-modules-user-ts-实际代码-" aria-label="Permalink to "存储(摘自 \`ui/src/store/modules/user.ts\` 实际代码)""></a></h3><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 登录成功后存储(仅 access_token)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> ex</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 7</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 24</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 60</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 60</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1000</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">set</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">ACCESS_TOKEN</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> data</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">access_token</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> ex</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 键名 'ACCESS-TOKEN'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 登出时清除(userStore.logout)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">remove</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">ACCESS_TOKEN</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">remove</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">CURRENT_USER</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">clear</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span></code></pre></div><h3 id="请求拦截器-自动携带-摘自-ui-src-utils-http-axios-index-ts-" tabindex="-1">请求拦截器(自动携带,摘自 <code>ui/src/utils/http/axios/index.ts</code>) <a class="header-anchor" href="#请求拦截器-自动携带-摘自-ui-src-utils-http-axios-index-ts-" aria-label="Permalink to "请求拦截器(自动携带,摘自 \`ui/src/utils/http/axios/index.ts\`)""></a></h3><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> token</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">get</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">ACCESS-TOKEN</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> ''</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">config</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">headers</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">[</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B5695977;"> \`</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Bearer </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">\${</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B56959;">token</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">}</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B5695977;">\`</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span></code></pre></div><h3 id="响应拦截器-401-处理-摘自-ui-src-utils-http-axios-index-ts-" tabindex="-1">响应拦截器(401 处理,摘自 <code>ui/src/utils/http/axios/index.ts</code>) <a class="header-anchor" href="#响应拦截器-401-处理-摘自-ui-src-utils-http-axios-index-ts-" aria-label="Permalink to "响应拦截器(401 处理,摘自 \`ui/src/utils/http/axios/index.ts\`)""></a></h3><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">import</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> useDialog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> from</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">@/hooks/web/useDialog</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> $dialog</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> useDialog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// code === 401(ResultEnum.TIMEOUT)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">case</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> ResultEnum</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">TIMEOUT</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">:</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> if</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> (</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">router</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">currentRoute</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">value</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">?.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">name</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ===</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> PageEnum</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">BASE_LOGIN_NAME</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> $dialog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">warning</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">({</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#998418;"> title</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">提示</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#998418;"> content</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">登录身份已失效,请重新登录!</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#998418;"> positiveText</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">确定</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> //negativeText: '取消',</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#998418;"> closable</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> false</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#998418;"> maskClosable</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> false</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> onPositiveClick</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> () </span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">clear</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> window</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">location</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">href</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> PageEnum</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#B07D48;">BASE_LOGIN</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">; </span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// '/login'</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> },</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> onNegativeClick</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> () </span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {},</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> });</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> break</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span></code></pre></div><h3 id="SSO-场景-URL-参数传递-Token-" tabindex="-1">SSO 场景(URL 参数传递 Token) <a class="header-anchor" href="#SSO-场景-URL-参数传递-Token-" aria-label="Permalink to "SSO 场景(URL 参数传递 Token)""></a></h3><p>路由守卫检测 <code>?token=</code> 参数并写入存储(摘自 <code>ui/src/router/router-guards.ts</code> 实际代码):</p><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">if</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> (</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">to</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">query</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> &&</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">token</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> in</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> to</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">query</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> const</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> tokenVal</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> String</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">to</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">query</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">token</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> storage</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">set</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">ACCESS_TOKEN</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> tokenVal</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;"> userStore</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">.</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">setToken</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">tokenVal</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h2 id="Token-Payload-结构" tabindex="-1">Token Payload 结构 <a class="header-anchor" href="#Token-Payload-结构" aria-label="Permalink to "Token Payload 结构""></a></h2><h3 id="access-token" tabindex="-1">access_token <a class="header-anchor" href="#access-token" aria-label="Permalink to "access_token""></a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">access</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jti</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">550e8400-e29b-41d4-a716-446655440000</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iss</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">rxthinkcmf</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iat</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1727000000</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1727007200</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="refresh-token" tabindex="-1">refresh_token <a class="header-anchor" href="#refresh-token" aria-label="Permalink to "refresh_token""></a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">refresh</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jti</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">550e8400-e29b-41d4-a716-446655440000</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iss</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">rxthinkcmf</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iat</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1727000000</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1727604800</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p>(同一登录的两个令牌共享 <code>jti</code>,Redis 键 <code>refresh:{uid}:{jti}</code>。)</p><h2 id="错误响应汇总" tabindex="-1">错误响应汇总 <a class="header-anchor" href="#错误响应汇总" aria-label="Permalink to "错误响应汇总""></a></h2><table tabindex="0"><thead><tr><th>场景</th><th>HTTP 状态</th><th>code</th><th>msg</th></tr></thead><tbody><tr><td>未提供 Token</td><td>200</td><td>401</td><td>未提供认证令牌</td></tr><tr><td>Token 过期</td><td>200</td><td>401</td><td>Expired token</td></tr><tr><td>Token 签名无效</td><td>200</td><td>401</td><td>Signature verification failed</td></tr><tr><td>Token 类型错误(refresh 当 access 用)</td><td>200</td><td>401</td><td>无效的access_token</td></tr><tr><td>无权限</td><td><strong>403</strong></td><td>403</td><td>无访问权限:{权限码}</td></tr><tr><td>refresh_token 无效/类型错</td><td>200</td><td>1</td><td>无效的refresh_token</td></tr><tr><td>refresh_token 已用/已吊销</td><td>200</td><td>1</td><td>refresh_token已失效</td></tr></tbody></table><h2 id="配置项" tabindex="-1">配置项 <a class="header-anchor" href="#配置项" aria-label="Permalink to "配置项""></a></h2><table tabindex="0"><thead><tr><th>配置键(<code>config/jwt.php</code>)</th><th>环境变量</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>jwt.secret</code></td><td><code>JWT_SECRET</code></td><td>(必填)</td><td>JWT 签名密钥,<strong>必须 ≥ 32 字符,否则拒绝启动</strong></td></tr><tr><td><code>jwt.access_ttl</code></td><td><code>JWT_ACCESS_TTL</code></td><td><code>7200</code></td><td>access_token 有效期(秒)</td></tr><tr><td><code>jwt.refresh_ttl</code></td><td><code>JWT_REFRESH_TTL</code></td><td><code>604800</code></td><td>refresh_token 有效期(秒)</td></tr><tr><td><code>jwt.perms_ttl</code></td><td><code>JWT_PERMS_TTL</code></td><td><code>7200</code></td><td>权限缓存 TTL(秒)</td></tr><tr><td><code>jwt.issuer</code></td><td><code>JWT_ISSUER</code></td><td><code>rxthinkcmf</code></td><td>签发者标识</td></tr><tr><td><code>jwt.header_prefix</code></td><td>—</td><td><code>Bearer </code></td><td>请求头前缀</td></tr></tbody></table><div class="warning custom-block"><p class="custom-block-title">生产环境安全</p><p>生产环境务必通过 <code>.env</code> 设置 <code>JWT_SECRET</code> 为 ≥32 字符的强随机密钥(<strong>不满足应用拒绝启动</strong>):</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">openssl</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> rand</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -base64</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 32</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 或</span></span>
|
||
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">php</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -r</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> "</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">echo bin2hex(random_bytes(32));</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">"</span></span></code></pre></div></div>`,81)])])}const F=i(h,[["render",p]]);export{c as __pageData,F as default};
|