# OAuth2 API 接口文档 > 创建日期:2026-05-26 --- ## 概述 OAuth2 模块提供标准化的第三方登录认证功能,支持多种 OAuth Provider,采用配置驱动的架构设计。 **Base URL:** `/api/auth/oauth` --- ## 接口列表 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/:provider/authorize` | 生成授权 URL,重定向到 Provider | | GET | `/:provider/callback` | Provider 回调,处理 token 交换 | | POST | `/bind` | 已登录用户绑定 OAuth 账号 | | DELETE | `/:provider/unbind` | 解绑 OAuth 账号 | | GET | `/bindings` | 获取当前用户的绑定列表 | | GET | `/:provider/status` | 查询某 Provider 的绑定状态 | --- ## 1. 授权接口 ### 生成授权 URL ``` GET /api/auth/oauth/:provider/authorize ``` **路径参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | provider | string | 是 | Provider 名称,如 `github`、`google` | **查询参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | bind | number | 否 | 绑定模式,传递 `1` 表示已登录用户绑定操作 | | redirect_uri | string | 否 | 自定义回调跳转地址 | **响应:** `302 Found` ``` Location: https://github.com/login/oauth/authorize?client_id=xxx&redirect_uri=xxx&scope=xxx&state=xxx ``` **错误响应:** | 状态码 | 错误码 | 说明 | |--------|--------|------| | 400 | `OAUTH_PROVIDER_NOT_FOUND` | 不支持的 Provider | --- ## 2. 回调接口 ### 处理 Provider 回调 ``` GET /api/auth/oauth/:provider/callback ``` **路径参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | provider | string | 是 | Provider 名称 | **查询参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | code | string | 是 | Provider 授权码 | | state | string | 是 | 防止 CSRF 的状态令牌 | **响应:** `302 Found` **登录成功:** ``` Location: /auth/login?oauth_success=1 ``` **绑定成功:** ``` Location: /profile?bind_success=1 ``` **错误响应:** | 状态码 | 错误码 | 说明 | |--------|--------|------| | 400 | `OAUTH_STATE_INVALID` | state 验证失败 | | 400 | `OAUTH_STATE_EXPIRED` | state 已过期 | | 400 | `OAUTH_TOKEN_EXCHANGE_FAILED` | token 交换失败 | | 400 | `OAUTH_USER_INFO_FAILED` | 获取用户信息失败 | | 400 | `OAUTH_ALREADY_BIND` | 账号已绑定其他用户 | | 400 | `OAUTH_BINDING_USER_MISMATCH` | 绑定用户不匹配 | 错误跳转示例: ``` Location: /auth/login?oauth_error=OAUTH_STATE_EXPIRED ``` --- ## 3. 绑定接口 ### 绑定 OAuth 账号 ``` POST /api/auth/oauth/bind ``` **请求体:** ```json { "bindingToken": "string" } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | bindingToken | string | 是 | 回调时返回的临时 binding token | **响应:** ```json { "success": true, "binding": { "provider": "github", "username": "username" } } ``` **错误响应:** | 状态码 | 错误码 | 说明 | |--------|--------|------| | 400 | `OAUTH_BINDING_TOKEN_INVALID` | binding token 无效或已过期 | | 401 | `UNAUTHORIZED` | 未登录 | --- ## 4. 解绑接口 ### 解绑 OAuth 账号 ``` DELETE /api/auth/oauth/:provider/unbind ``` **路径参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | provider | string | 是 | Provider 名称 | **响应:** ```json { "success": true } ``` **错误响应:** | 状态码 | 错误码 | 说明 | |--------|--------|------| | 400 | `OAUTH_LAST_BIND` | 至少保留一个绑定方式(不能解绑最后一个) | | 401 | `UNAUTHORIZED` | 未登录 | --- ## 5. 绑定列表接口 ### 获取绑定列表 ``` GET /api/auth/oauth/bindings ``` **响应:** ```json { "bindings": [ { "id": 1, "provider": "github", "username": "username", "email": "user@example.com", "avatar": "https://avatars.githubusercontent.com/u/xxx", "boundAt": "2026-05-26T10:00:00.000Z" } ] } ``` --- ## 6. 绑定状态接口 ### 查询单个 Provider 绑定状态 ``` GET /api/auth/oauth/:provider/status ``` **路径参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | provider | string | 是 | Provider 名称 | **响应:** ```json { "bound": true, "binding": { "id": 1, "username": "username" } } ``` 或未绑定: ```json { "bound": false } ``` --- ## 错误码详解 | 错误码 | HTTP 状态码 | 说明 | |--------|-------------|------| | `OAUTH_PROVIDER_NOT_FOUND` | 400 | 请求的 Provider 未注册 | | `OAUTH_STATE_INVALID` | 400 | state 令牌无效,可能被篡改 | | `OAUTH_STATE_EXPIRED` | 400 | state 超过 5 分钟有效期 | | `OAUTH_TOKEN_EXCHANGE_FAILED` | 400 | 与 Provider 交换 access_token 失败 | | `OAUTH_USER_INFO_FAILED` | 400 | 从 Provider 获取用户信息失败 | | `OAUTH_ALREADY_BIND` | 400 | 该第三方账号已绑定其他用户 | | `OAUTH_BINDING_USER_MISMATCH` | 400 | 绑定操作用户与当前登录用户不匹配 | | `OAUTH_BINDING_TOKEN_INVALID` | 400 | binding token 无效或已过期 | | `OAUTH_LAST_BIND` | 400 | 无法解绑最后一个 OAuth 账号 | | `UNAUTHORIZED` | 401 | 需要登录才能进行此操作 | --- ## 使用示例 ### 前端调用流程 #### 1. 发起 GitHub 登录 ```typescript // 方法一:直接跳转(后端处理一切) window.location.href = '/api/auth/oauth/github/authorize'; // 方法二:打开弹窗 const popup = window.open('/api/auth/oauth/github/authorize', 'oauth', 'width=600,height=700'); ``` #### 2. 回调后处理 ```typescript // 在登录页或 profile 页检测参数 const url = new URL(window.location.href); if (url.searchParams.get('oauth_success') === '1') { // 刷新用户状态 await refreshSession(); // 跳转到首页或用户中心 router.push('/'); } if (url.searchParams.get('bind_success') === '1') { // 刷新绑定列表 await fetchBindings(); } if (url.searchParams.get('oauth_error')) { const error = url.searchParams.get('oauth_error'); alert(`登录失败: ${error}`); } ``` #### 3. 获取绑定列表 ```typescript const { data } = await useFetch('/api/auth/oauth/bindings'); const bindings = data.value?.bindings ?? []; ``` #### 4. 解绑操作 ```typescript await $fetch('/api/auth/oauth/github/unbind', { method: 'DELETE' }); // 刷新绑定列表 await fetchBindings(); ```