Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/.vitepress/dist/assets/zh_api-spec_token.md.C8eA4flk.js
T
2026-10-11 19:06:33 +08:00

240 lines
85 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 &quot;8.6 Token 使用说明&quot;">​</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 &quot;Token 完整生命周期&quot;">​</a></h2><h3 id="登录获取-Token" tabindex="-1">登录获取 Token <a class="header-anchor" href="#登录获取-Token" aria-label="Permalink to &quot;登录获取 Token&quot;">​</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(&#39;username&#39;) 查询用户 │</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>│ &quot;access_token&quot;: &quot;eyJ...&quot;, ← 用于接口认证 │</span></span>
<span class="line"><span>│ &quot;refresh_token&quot;: &quot;eyJ...&quot;, ← 用于刷新令牌 │</span></span>
<span class="line"><span>│ &quot;token_type&quot;: &quot;Bearer&quot;, │</span></span>
<span class="line"><span>│ &quot;expires_in&quot;: 7200, ← 2小时 = 7200秒 │</span></span>
<span class="line"><span>│ &quot;user&quot;: { &quot;id&quot;:1, &quot;username&quot;:&quot;admin&quot;, ... } │</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(&#39;ACCESS-TOKEN&#39;, 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 &quot;使用 Token 请求 API&quot;">​</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>用户操作(如点击&quot;用户列表&quot;)</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(&#39;ACCESS-TOKEN&#39;) │</span></span>
<span class="line"><span>│ config.headers[&#39;Authorization&#39;] = \`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&amp;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 &quot;未提供认证令牌&quot; │</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[&#39;userInfo&#39;] │</span></span>
<span class="line"><span>│ 4. 反射 #[Permission] → checkPermission() │</span></span>
<span class="line"><span>│ │ 无权限 → HTTP 403 &quot;无访问权限:{权限码}&quot; │</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 → 弹窗&quot;登录身份已失效&quot; → 清存储 → 跳登录页 │</span></span>
<span class="line"><span>│ code/status 403 → 提示&quot;无访问权限&quot; │</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 &quot;Token 刷新(后端能力)&quot;">​</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>{ &quot;refresh_token&quot;: &quot;eyJ...&quot; } ← 主入口(公开接口)</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 → 失败 → &quot;无效的refresh_token&quot;</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> │ 已用过/已吊销 → &quot;refresh_token已失效&quot;</span></span>
<span class="line"><span> │ 4. 用户状态校验 → &quot;用户不存在或已被禁用&quot;</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 &quot;Token 失效(重新登录)&quot;">​</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 = &#39;/login&#39; │</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 &quot;两种 Token 的区别&quot;">​</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 &lt;token&gt; │</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 &quot;请求头格式&quot;">​</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 &lt;access_token&gt;</span></span></code></pre></div><h3 id="示例" tabindex="-1">示例 <a class="header-anchor" href="#示例" aria-label="Permalink to &quot;示例&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">http://admin.laravel13.naivevue/api/user/page?pageNo=1&amp;pageSize=10</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span></span></code></pre></div><h3 id="请求头配置" tabindex="-1">请求头配置 <a class="header-anchor" href="#请求头配置" aria-label="Permalink to &quot;请求头配置&quot;">​</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 &quot;Token 获取&quot;">​</a></h2><h3 id="登录获取-主入口-" tabindex="-1">登录获取(主入口) <a class="header-anchor" href="#登录获取-主入口-" aria-label="Permalink to &quot;登录获取(主入口)&quot;">​</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;"> &quot;username&quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> &quot;password&quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">123456</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> &quot;code&quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">a3Bx</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">,</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> &quot;key&quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">captcha_key_xxx</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">登录成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">access_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">refresh_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">token_type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Bearer</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">expires_in</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">user</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">id</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">realname</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">管理员</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">avatar</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">user/avatar/20260923/100429_e8FAKf9r.png</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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 &quot;OAuth2 密码模式获取&quot;">​</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;">&amp;</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;">&amp;</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 &quot;响应字段&quot;">​</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 &quot;Token 刷新&quot;">​</a></h2><p>access_token 过期后(code=401),使用 refresh_token 换取新令牌对:</p><h3 id="接口信息" tabindex="-1">接口信息 <a class="header-anchor" href="#接口信息" aria-label="Permalink to &quot;接口信息&quot;">​</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 &quot;请求示例&quot;">​</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;"> &quot;refresh_token&quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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;">&amp;</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 &quot;成功响应&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">刷新成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">access_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ...(新access_token)</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">refresh_token</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">eyJ...(新refresh_token)</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">token_type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Bearer</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">expires_in</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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 &quot;失败响应&quot;">​</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 &quot;Token 有效性检查&quot;">​</a></h2><h3 id="接口信息-1" tabindex="-1">接口信息 <a class="header-anchor" href="#接口信息-1" aria-label="Permalink to &quot;接口信息&quot;">​</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 &quot;请求示例&quot;">​</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;"> &lt;</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;">&gt;</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;">&lt;</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;">&gt;</span></span></code></pre></div><h3 id="成功响应-有效-" tabindex="-1">成功响应(有效) <a class="header-anchor" href="#成功响应-有效-" aria-label="Permalink to &quot;成功响应(有效)&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">操作成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">valid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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 &quot;失败响应(无效)&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">操作成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">valid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Expired token</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</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 &quot;Token 吊销&quot;">​</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;"> &lt;</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;">&gt;</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 &quot;URL 参数传递(SSO 场景)&quot;">​</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=&lt;access_token&gt;</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 &quot;前端存储与使用&quot;">​</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 &quot;存储(摘自 \`ui/src/store/modules/user.ts\` 实际代码)&quot;">​</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;"> // 键名 &#39;ACCESS-TOKEN&#39;</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 &quot;请求拦截器(自动携带,摘自 \`ui/src/utils/http/axios/index.ts\`)&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">ACCESS-TOKEN</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;&#39;</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">Authorization</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;响应拦截器(401 处理,摘自 \`ui/src/utils/http/axios/index.ts\`)&quot;">​</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">@/hooks/web/useDialog</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">提示</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">登录身份已失效,请重新登录!</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">确定</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> //negativeText: &#39;取消&#39;,</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;">=&gt;</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;">// &#39;/login&#39;</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;">=&gt;</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 &quot;SSO 场景(URL 参数传递 Token)&quot;">​</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;"> &amp;&amp;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">token</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;Token Payload 结构&quot;">​</a></h2><h3 id="access-token" tabindex="-1">access_token <a class="header-anchor" href="#access-token" aria-label="Permalink to &quot;access_token&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">access</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jti</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">550e8400-e29b-41d4-a716-446655440000</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iss</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">rxthinkcmf</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iat</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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 &quot;refresh_token&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">uid</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">username</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">admin</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">type</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">refresh</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jti</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">550e8400-e29b-41d4-a716-446655440000</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iss</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">rxthinkcmf</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">iat</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">exp</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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 &quot;错误响应汇总&quot;">​</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 &quot;配置项&quot;">​</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;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">echo bin2hex(random_bytes(32));</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span></span></code></pre></div></div>`,81)])])}const F=i(h,[["render",p]]);export{c as __pageData,F as default};