[{"data":1,"prerenderedAt":53},["ShallowReactive",2],{"doc:\u002Fcontracts\u002Foauth\u002Fnuxt-integration-prompt":3},{"title":4,"route":5,"toc":6,"segments":48,"source":52},"鲲 Galgame OAuth 接入 — Nuxt 项目实施提示词","\u002Fcontracts\u002Foauth\u002Fnuxt-integration-prompt",[7,10,13,15,17,21,24,27,30,33,36,39,42,44,46],{"id":8,"text":8,"depth":9},"任务",2,{"id":11,"text":12,"depth":9},"oauth-server-信息","OAuth Server 信息",{"id":14,"text":14,"depth":9},"端点",{"id":16,"text":16,"depth":9},"需要实现的文件",{"id":18,"text":19,"depth":20},"1-环境变量","1. 环境变量",3,{"id":22,"text":23,"depth":20},"2-pkce-工具函数-客户端","2. PKCE 工具函数（客户端）",{"id":25,"text":26,"depth":20},"3-登录触发-客户端","3. 登录触发（客户端）",{"id":28,"text":29,"depth":20},"4-回调页面-客户端","4. 回调页面（客户端）",{"id":31,"text":32,"depth":20},"5-服务端回调处理","5. 服务端回调处理",{"id":34,"text":35,"depth":20},"6-token-刷新","6. Token 刷新",{"id":37,"text":38,"depth":20},"7-登出","7. 登出",{"id":40,"text":41,"depth":9},"api-响应格式","API 响应格式",{"id":43,"text":43,"depth":9},"错误处理",{"id":45,"text":45,"depth":9},"安全要求",{"id":47,"text":47,"depth":9},"数据库",[49],{"type":50,"html":51},"html","\u003Ch1 id=\"鲲-galgame-oauth-接入-nuxt-项目实施提示词\" tabindex=\"-1\">鲲 Galgame OAuth 接入 — Nuxt 项目实施提示词\u003C\u002Fh1>\n\u003Cblockquote>\n\u003Cp>本文档是给 AI 编码助手（如 Claude）的提示词，用于在 Nuxt 3\u002F4 项目中实现 鲲 Galgame OAuth 登录功能。\n将本文件内容作为 context 提供给 AI，它就能正确编写对接代码。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Chr>\n\u003Ch2 id=\"任务\" tabindex=\"-1\">任务\u003C\u002Fh2>\n\u003Cp>在当前 Nuxt 项目中接入 鲲 Galgame OAuth 2.0 登录系统，实现「使用 KUN 账号登录」功能。\u003C\u002Fp>\n\u003Ch2 id=\"oauth-server-信息\" tabindex=\"-1\">OAuth Server 信息\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>生产环境 Base URL\u003C\u002Fstrong>: \u003Ccode>https:\u002F\u002Foauth.kungal.com\u002Fapi\u002Fv1\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>开发环境 Base URL\u003C\u002Fstrong>: \u003Ccode>http:\u002F\u002F127.0.0.1:9277\u002Fapi\u002Fv1\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>协议\u003C\u002Fstrong>: OAuth 2.0 Authorization Code + PKCE (S256)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"端点\" tabindex=\"-1\">端点\u003C\u002Fh2>\n\u003Cdiv class=\"kun-table-wrap\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>端点\u003C\u002Fth>\n\u003Cth>方法\u003C\u002Fth>\n\u003Cth>说明\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>\u002Foauth\u002Fauthorize\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>GET\u003C\u002Ftd>\n\u003Ctd>获取授权码（用户必须已登录 OAuth Server）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>\u002Foauth\u002Ftoken\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>POST\u003C\u002Ftd>\n\u003Ctd>用授权码换取 token\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>\u002Foauth\u002Fuserinfo\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>GET\u003C\u002Ftd>\n\u003Ctd>用 access_token 获取用户信息\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>\u002Foauth\u002Frevoke\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>POST\u003C\u002Ftd>\n\u003Ctd>吊销 refresh_token\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Ch2 id=\"需要实现的文件\" tabindex=\"-1\">需要实现的文件\u003C\u002Fh2>\n\u003Ch3 id=\"1-环境变量\" tabindex=\"-1\">1. 环境变量\u003C\u002Fh3>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan># .env\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NUXT_OAUTH_SERVER_URL=https:\u002F\u002Foauth.kungal.com\u002Fapi\u002Fv1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NUXT_PUBLIC_OAUTH_CLIENT_ID=&#x3C;从管理后台获取>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NUXT_OAUTH_CLIENT_SECRET=&#x3C;从管理后台获取>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NUXT_PUBLIC_OAUTH_REDIRECT_URI=https:\u002F\u002Fwww.kungal.com\u002Fauth\u002Fcallback\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在 \u003Ccode>nuxt.config.ts\u003C\u002Fcode> 的 \u003Ccode>runtimeConfig\u003C\u002Fcode> 中配置：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-typescript\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">runtimeConfig\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">  oauthServerUrl\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: process.env.\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">NUXT_OAUTH_SERVER_URL\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">  oauthClientSecret\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: process.env.\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">NUXT_OAUTH_CLIENT_SECRET\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">  public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    oauthClientId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: process.env.\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">NUXT_PUBLIC_OAUTH_CLIENT_ID\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    oauthRedirectUri\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: process.env.\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">NUXT_PUBLIC_OAUTH_REDIRECT_URI\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  },\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">},\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"2-pkce-工具函数-客户端\" tabindex=\"-1\">2. PKCE 工具函数（客户端）\u003C\u002Fh3>\n\u003Cp>创建 \u003Ccode>utils\u002Foauth-pkce.ts\u003C\u002Fcode>，包含：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>generateCodeVerifier()\u003C\u002Fcode> — 生成 43-128 字符随机字符串（base64url 编码）\u003C\u002Fli>\n\u003Cli>\u003Ccode>generateCodeChallenge(verifier)\u003C\u002Fcode> — SHA256(verifier) 的 base64url 编码\u003C\u002Fli>\n\u003Cli>\u003Ccode>generateState()\u003C\u002Fcode> — 16 字节随机 hex 字符串\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>使用 \u003Ccode>crypto.getRandomValues()\u003C\u002Fcode> 和 \u003Ccode>crypto.subtle.digest('SHA-256', ...)\u003C\u002Fcode> 实现。\u003C\u002Fp>\n\u003Ch3 id=\"3-登录触发-客户端\" tabindex=\"-1\">3. 登录触发（客户端）\u003C\u002Fh3>\n\u003Cp>在登录页面添加一个「使用 KUN 账号登录」按钮，点击后：\u003C\u002Fp>\n\u003Col>\n\u003Cli>调用 \u003Ccode>generateCodeVerifier()\u003C\u002Fcode> 和 \u003Ccode>generateCodeChallenge()\u003C\u002Fcode> 和 \u003Ccode>generateState()\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>将 \u003Ccode>code_verifier\u003C\u002Fcode> 和 \u003Ccode>state\u003C\u002Fcode> 存入 \u003Ccode>sessionStorage\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>构建授权 URL 并跳转：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan>{oauthServerUrl}\u002Foauth\u002Fauthorize?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  client_id={clientId}&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  redirect_uri={redirectUri}&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  response_type=code&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  scope=openid+profile&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  state={state}&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  code_challenge={codeChallenge}&#x26;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  code_challenge_method=S256\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"4-回调页面-客户端\" tabindex=\"-1\">4. 回调页面（客户端）\u003C\u002Fh3>\n\u003Cp>创建 \u003Ccode>pages\u002Fauth\u002Fcallback.vue\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Col>\n\u003Cli>从 URL query 读取 \u003Ccode>code\u003C\u002Fcode> 和 \u003Ccode>state\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>从 \u003Ccode>sessionStorage\u003C\u002Fcode> 读取之前保存的 \u003Ccode>state\u003C\u002Fcode> 和 \u003Ccode>code_verifier\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>验证 \u003Ccode>state\u003C\u002Fcode> 一致性\u003C\u002Fli>\n\u003Cli>调用自己的服务端 API \u003Ccode>\u002Fapi\u002Fauth\u002Foauth-callback\u003C\u002Fcode>，传入 \u003Ccode>code\u003C\u002Fcode> 和 \u003Ccode>code_verifier\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>成功后跳转首页，失败时跳转登录页显示错误\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"5-服务端回调处理\" tabindex=\"-1\">5. 服务端回调处理\u003C\u002Fh3>\n\u003Cp>创建 \u003Ccode>server\u002Fapi\u002Fauth\u002Foauth-callback.post.ts\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Col>\n\u003Cli>接收客户端传来的 \u003Ccode>code\u003C\u002Fcode> 和 \u003Ccode>code_verifier\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>调用 OAuth Server 的 \u003Ccode>\u002Foauth\u002Ftoken\u003C\u002Fcode>：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-json\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">{\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"grant_type\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"authorization_code\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"code\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"&#x3C;授权码>\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"redirect_uri\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"&#x3C;回调地址>\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"client_id\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"&#x3C;客户端ID>\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"client_secret\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"&#x3C;客户端密钥>\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"code_verifier\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"&#x3C;PKCE验证器>\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col start=\"3\">\n\u003Cli>用返回的 \u003Ccode>data.access_token\u003C\u002Fcode> 调用 \u003Ccode>\u002Foauth\u002Fuserinfo\u003C\u002Fcode> 获取用户信息：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-json\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F GET \u002Foauth\u002Fuserinfo, Authorization: Bearer &#x3C;access_token>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 响应:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">{\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"code\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"message\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"成功\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"data\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"sub\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用户UUID（唯一标识）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"name\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用户名\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"email\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"邮箱\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"picture\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"头像URL\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"updated_at\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">1234567890\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col start=\"4\">\n\u003Cli>用 \u003Ccode>sub\u003C\u002Fcode>（用户UUID）在本站数据库查找或创建用户\u003C\u002Fli>\n\u003Cli>创建本站 session\u003C\u002Fli>\n\u003Cli>将 OAuth 的 \u003Ccode>refresh_token\u003C\u002Fcode> 安全存储（用于后续刷新）\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"6-token-刷新\" tabindex=\"-1\">6. Token 刷新\u003C\u002Fh3>\n\u003Cp>当 access_token 过期（15 分钟）时，调用：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan>POST \u002Foauth\u002Ftoken\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>{\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  \"grant_type\": \"refresh_token\",\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  \"refresh_token\": \"&#x3C;之前保存的>\",\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  \"client_id\": \"&#x3C;客户端ID>\",\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  \"client_secret\": \"&#x3C;客户端密钥>\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>重要\u003C\u002Fstrong>：每次刷新会返回新的 \u003Ccode>refresh_token\u003C\u002Fcode>（令牌轮换），必须更新保存的值。\u003C\u002Fp>\n\u003Ch3 id=\"7-登出\" tabindex=\"-1\">7. 登出\u003C\u002Fh3>\n\u003Cp>用户在本站登出时，同时吊销 OAuth 令牌：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-text\">\u003Cspan class=\"line\">\u003Cspan>POST \u002Foauth\u002Frevoke\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>{ \"token\": \"&#x3C;refresh_token>\" }\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"api-响应格式\" tabindex=\"-1\">API 响应格式\u003C\u002Fh2>\n\u003Cp>所有 OAuth Server 的 API 响应使用统一格式：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-json\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">{\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"code\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,          \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 0=成功, 非零=错误码\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"message\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"成功\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"data\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">: { \u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic\">...\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>包括 \u003Ccode>\u002Foauth\u002Ftoken\u003C\u002Fcode> 和 \u003Ccode>\u002Foauth\u002Fuserinfo\u003C\u002Fcode>，实际数据都在 \u003Ccode>data\u003C\u002Fcode> 字段中。\u003C\u002Fp>\n\u003Ch2 id=\"错误处理\" tabindex=\"-1\">错误处理\u003C\u002Fh2>\n\u003Cdiv class=\"kun-table-wrap\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>错误码\u003C\u002Fth>\n\u003Cth>含义\u003C\u002Fth>\n\u003Cth>处理\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>15001\u003C\u002Ftd>\n\u003Ctd>无效的客户端\u003C\u002Ftd>\n\u003Ctd>检查 client_id\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>15002\u003C\u002Ftd>\n\u003Ctd>无效的回调地址\u003C\u002Ftd>\n\u003Ctd>检查 redirect_uri 是否已注册\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>15003\u003C\u002Ftd>\n\u003Ctd>无效的授权码\u003C\u002Ftd>\n\u003Ctd>重新走授权流程\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>15004\u003C\u002Ftd>\n\u003Ctd>PKCE 验证失败\u003C\u002Ftd>\n\u003Ctd>检查 code_verifier 生成逻辑\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>10003\u003C\u002Ftd>\n\u003Ctd>令牌已过期\u003C\u002Ftd>\n\u003Ctd>重新登录\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Ch2 id=\"安全要求\" tabindex=\"-1\">安全要求\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Ccode>client_secret\u003C\u002Fcode> 只能在服务端使用（\u003Ccode>server\u002F\u003C\u002Fcode> 目录下），绝不暴露到客户端\u003C\u002Fli>\n\u003Cli>始终验证 \u003Ccode>state\u003C\u002Fcode> 参数防止 CSRF\u003C\u002Fli>\n\u003Cli>\u003Ccode>refresh_token\u003C\u002Fcode> 存储在 httpOnly cookie 或服务端 session 中\u003C\u002Fli>\n\u003Cli>使用 PKCE (S256) 即使有 client_secret\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"数据库\" tabindex=\"-1\">数据库\u003C\u002Fh2>\n\u003Cp>本站需要一张表关联 OAuth 用户和本站用户：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode class=\"language-sql\">\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">CREATE\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> TABLE\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> oauth_accounts\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  id \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">SERIAL\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> PRIMARY KEY\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  user_id \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">INTEGER\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> REFERENCES\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> users(id),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">  provider\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> VARCHAR\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">50\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">NOT NULL\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> DEFAULT\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> 'kun-oauth'\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  provider_account_id \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">VARCHAR\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">255\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">NOT NULL\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">-- 存 userinfo 返回的 sub (UUID)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  access_token \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">TEXT\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  refresh_token \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">TEXT\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  expires_at \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">TIMESTAMP\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">  created_at \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">TIMESTAMP\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> DEFAULT\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> NOW\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">  UNIQUE\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">provider\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, provider_account_id)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>用 \u003Ccode>provider_account_id\u003C\u002Fcode>（即 userinfo 的 \u003Ccode>sub\u003C\u002Fcode> 字段）作为用户在 OAuth 系统中的唯一标识。\u003C\u002Fp>\n","kun-galgame-infra\u002Fdocs\u002Fintegration\u002Foauth\u002Fnuxt-integration-prompt.md",1781708342289]