# OAuth2 模块化功能设计方案 > 创建日期:2026-05-26 > 状态:已批准 --- ## 一、架构概览 ``` ┌─────────────────────────────────────────────────────────┐ │ 前端 (Vue) │ │ /pages/auth/login.vue (集成 GitHub 登录按钮) │ └─────────────────────┬───────────────────────────────────┘ │ 1. 点击登录 ▼ ┌─────────────────────────────────────────────────────────┐ │ 后端 API (Nitro) │ │ ┌─────────────────────────────────────────────────┐ │ │ │ OAuthModule (核心调度器) │ │ │ │ - registerProvider() 注册 Provider │ │ │ │ - getAuthorizationUrl() 生成授权 URL │ │ │ │ - handleCallback() 处理回调 │ │ │ │ - bindAccount() 绑定账号 │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ GitHubProvider │ │ GoogleProvider │ ... (可扩展) │ │ │ (implements) │ │ (implements) │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ OAuthStateStore (state 临时存储,5min TTL) │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ AuthService (现有认证服务) │ │ │ │ - getCurrentUser() │ │ │ │ - loginUser() / registerUser() │ │ │ └─────────────────────────────────────────────────┘ │ └─────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 数据库 (SQLite) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ users │ │ sessions │ │oauth_accounts│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` **核心设计原则:** - **配置驱动** — 内置 Provider 通过 `oauth.providers.ts` 配置注册,零代码添加新 Provider - **策略模式** — 每个 Provider 是独立策略类,实现统一接口 - **最小改动** — 集成到现有登录页,不影响现有认证逻辑 --- ## 二、核心模块设计 ### 2.1 OAuth Provider 接口 ```typescript // packages/oauth/src/providers/base.ts export interface OAuthProvider { name: string; // 'github' | 'google' icon: string; // emoji 或 Icon 组件名 clientId: string; clientSecret: string; authorizeUrl: string; tokenUrl: string; userInfoUrl: string; scopes: string[]; // 请求的权限范围 // 特定字段映射(不同 Provider 的返回字段不同) mapUserInfo(raw: Record): OAuthUserInfo; } export type OAuthUserInfo = { provider: string; providerUserId: string; // 第三方平台的用户 ID username?: string; email?: string; avatar?: string; }; ``` ### 2.2 OAuth 模块核心 ```typescript // packages/oauth/src/core/oauth-manager.ts export class OAuthManager { // 注册 Provider registerProvider(provider: OAuthProvider): void; // 生成授权 URL(含 state) async getAuthorizationUrl(providerName: string): Promise; // 处理回调(交换 token,获取用户信息) async handleCallback(providerName: string, code: string, state: string): Promise; // 绑定账号(已登录用户绑定第三方账号) async bindAccount(userId: number, providerName: string, providerUserId: string): Promise; // 解绑账号 async unbindAccount(userId: number, providerName: string): Promise; // 获取用户已绑定的 OAuth 账号列表 async getUserBindings(userId: number): Promise; } export type OAuthCallbackResult = { success: boolean; isNewUser: boolean; user?: MinimalUser; requiresBinding: boolean; // 账号已存在但未绑定,需用户确认 bindingToken?: string; // 临时 binding token }; export type OAuthBinding = { id: number; provider: string; username: string | null; email: string | null; avatar: string | null; boundAt: Date; }; ``` ### 2.3 State 管理(防止 CSRF) ```typescript // packages/oauth/src/core/oauth-state-store.ts // 内存 Map + 5min TTL 过期清理 // State 包含: providerName, redirectUri, createdAt, userId(可选) ``` State 存储结构: ```typescript type OAuthState = { providerName: string; redirectUri: string; createdAt: number; // timestamp userId?: number; // 绑定模式下才有值 isBinding: boolean; // 是否为绑定模式 }; ``` --- ## 三、数据库设计 ### 3.1 oauth_accounts 表 ```sql CREATE TABLE `oauth_accounts` ( `id` integer PRIMARY KEY AUTOINCREMENT, `user_id` integer NOT NULL, `provider` text NOT NULL, `provider_user_id` text NOT NULL, `username` text, `email` text, `avatar` text, `created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, `updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, UNIQUE(`provider`, `provider_user_id`), FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ); CREATE INDEX `oauth_accounts_user_id_idx` ON `oauth_accounts` (`user_id`); ``` **索引设计:** `provider + provider_user_id` 唯一索引确保不能重复绑定。 --- ## 四、API 设计 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/auth/oauth/:provider/authorize` | 生成授权 URL,重定向到 GitHub | | GET | `/api/auth/oauth/:provider/callback` | GitHub 回调,处理 token 交换 | | POST | `/api/auth/oauth/bind` | 已登录用户绑定 OAuth 账号 | | DELETE | `/api/auth/oauth/:provider/unbind` | 解绑 OAuth 账号 | | GET | `/api/auth/oauth/bindings` | 获取当前用户的绑定列表 | | GET | `/api/auth/oauth/:provider/status` | 查询某 Provider 的绑定状态 | ### 4.1 授权接口 ``` GET /api/auth/oauth/:provider/authorize Query Parameters: - bind: number (可选) 绑定模式下传递 1 - redirect_uri: string (可选) 自定义回调跳转地址 Response: 302 重定向到 Provider 授权页面 ``` ### 4.2 回调接口 ``` GET /api/auth/oauth/:provider/callback Query Parameters: - code: string Provider 授权码 - state: string 防止 CSRF 的状态令牌 Response: 302 重定向到前端页面 - 登录成功:/auth/login?oauth_success=1 - 绑定成功:/profile?bind_success=1 - 失败:/auth/login?oauth_error=xxx ``` ### 4.3 绑定接口 ``` POST /api/auth/oauth/bind Body: { bindingToken: string; // 回调时返回的临时 token } Response: { success: true, binding: { provider: string; username: string | null; } } ``` ### 4.4 解绑接口 ``` DELETE /api/auth/oauth/:provider/unbind Response: { success: true } ``` ### 4.5 绑定列表接口 ``` GET /api/auth/oauth/bindings Response: { bindings: [ { id: number; provider: string; username: string | null; email: string | null; avatar: string | null; boundAt: Date; } ] } ``` ### 4.6 单个 Provider 绑定状态 ``` GET /api/auth/oauth/:provider/status Response: { bound: boolean; binding?: { id: number; username: string | null; } } ``` --- ## 五、流程图 ### 5.1 新用户 OAuth 登录流程 ``` ┌──────────────────────────────────────────────────────────────────┐ │ GitHub OAuth 登录流程 │ └──────────────────────────────────────────────────────────────────┘ 用户 前端 后端 GitHub │ │ │ │ │ 点击 GitHub 登录按钮 │ │ │ │──────────────────────>│ │ │ │ │ │ │ │ │ GET /api/auth/oauth/github/authorize │ │ │────────────────────────────────────────────> │ │ │ │ │ │ │ 1. 生成 state │ │ │ │ 2. 存储 state │ │ │ │ 3. 拼接授权 URL │ │ │ │<──────────────────────────────────────────── │ │ │ │ │<──────────────────────│ 302 重定向到 GitHub │ │ │ │ │ │ │ 在 GitHub 授权页面 │ │ │ │ 确认授权 │ │ │ │───────────────────────│───────────────────────│──────────────────────> │ │ │ │ │ │ │ GitHub 回调 │ │ │ │ ?code=xxx&state=xxx │ │ │ │<──────────────────────│ │ │ │ │ │ │ │ 验证 state │ │ │ │ 用 code 换 token │ │ │ │ 获取用户信息 │ │ │ │──────────────────────> │ │ │ │ │ │ │ 创建/查找用户 │ │ │ │ 创建 session │ │ │ │ │ │<──────────────────────│ 302 重定向到 /auth/login?oauth_success=1 │ │ │ │ │ │ 检测 url参数 │ │ │ │ 刷新 session │ │ │ │──────────────────────>│ │ │ │ │ │ │ │ │ GET /api/auth/session │ │ │────────────────────────────────────────────> │ │ │ │ │ │<─────────────────────────────────────────────│ 登录成功 │ │ │ │ │ 显示登录成功 │ │ │ │ │ │ │ ``` ### 5.2 已注册用户绑定 GitHub 流程 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 账号绑定流程 │ └──────────────────────────────────────────────────────────────────┘ 已登录用户 前端 后端 GitHub │ │ │ │ │ 点击绑定 GitHub │ │ │ │──────────────────────>│ │ │ │ │ │ │ │ │ GET /api/auth/oauth/github/authorize?bind=1 │ │ │────────────────────────────────────────────> │ │ │ │ │ │ │ 生成 binding state │ │ │ │ 包含 userId │ │ │ │ │ │<──────────────────────│ 302 重定向到 GitHub │ │ │ │ │ │ │ 授权 │ │ │ │───────────────────────│───────────────────────│──────────────────────> │ │ │ │ │ │ │ GitHub 回调 │ │ │ │ 验证 binding state │ │ │ │ 用 code 换 token │ │ │ │ 获取用户信息 │ │ │ │ │ │ │ │ 插入 oauth_accounts │ │ │ │ │ │<──────────────────────│ 302 重定向到 /profile?bind_success=1 │ │ │ │ │ │ 显示绑定成功 │ │ │ │ │ │ │ ``` ### 5.3 GitHub 用户映射关系 ``` GitHub API 返回字段 -> OAuthUserInfo 映射 (GitHubProvider) GitHub 返回: { "id": 123456, "login": "username", "email": "user@example.com", // 需要 user:email 权限 "avatar_url": "https://..." } 映射后: { provider: "github", providerUserId: "123456", username: "username", email: "user@example.com", avatar: "https://..." } ``` --- ## 六、现有登录页集成 在 `/pages/auth/login.vue` 增加: ```vue ``` --- ## 七、模块化扩展(添加 Google Provider) 只需在配置文件中添加: ```typescript // packages/oauth/src/providers/index.ts export const oauthProviders = { github: new GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET, }), google: new GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, scopes: ['openid', 'email', 'profile'], }), }; ``` **零代码新增 Provider** — 只需配置。 --- ## 八、错误处理 | 错误码 | 说明 | 处理方式 | |--------|------|----------| | `OAUTH_PROVIDER_NOT_FOUND` | 不支持的 Provider | 提示用户 | | `OAUTH_STATE_INVALID` | state 验证失败 | CSRF 攻击风险,清除跳转 | | `OAUTH_STATE_EXPIRED` | state 过期(>5min) | 提示重新授权 | | `OAUTH_TOKEN_EXCHANGE_FAILED` | token 交换失败 | 提示重试 | | `OAUTH_USER_INFO_FAILED` | 获取用户信息失败 | 提示重试 | | `OAUTH_ALREADY_BIND` | 账号已绑定其他用户 | 提示解绑后重试 | | `OAUTH_BINDING_USER_MISMATCH` | 绑定用户不匹配 | 重新登录授权 | --- ## 九、配置项 ```typescript // .env.example GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret // OAuth 配置 OAUTH_STATE_TTL_MS=300000 # 5 minutes OAUTH_CALLBACK_URL=/api/auth/oauth # 回调基础路径 ``` --- ## 十、文件结构 ``` packages/oauth/ ├── src/ │ ├── index.ts # 模块入口 │ ├── core/ │ │ ├── oauth-manager.ts # 核心管理器 │ │ ├── oauth-state-store.ts # State 存储 │ │ └── oauth-error.ts # 错误类 │ ├── providers/ │ │ ├── base.ts # Provider 接口 │ │ ├── github.ts # GitHub Provider │ │ ├── google.ts # Google Provider (预留) │ │ └── index.ts # Provider 导出 │ ├── db/ │ │ ├── schema.ts # oauth_accounts 表定义 │ │ └── queries.ts # 数据库操作 │ └── api/ │ ├── authorize.get.ts # 授权接口 │ ├── callback.get.ts # 回调接口 │ ├── bind.post.ts # 绑定接口 │ ├── unbind.delete.ts # 解绑接口 │ ├── bindings.get.ts # 绑定列表接口 │ └── status.get.ts # 绑定状态接口 ├── package.json └── tsconfig.json docs/superpowers/ ├── specs/ │ └── 2026-05-26-oauth2-design.md # 本设计文档 └── oauth2/ ├── README.md # 使用指南 ├── PROVIDERS.md # Provider 配置文档 ├── FLOW.md # 流程图详解 └── API.md # API 接口文档 ``` --- ## 十一、后续扩展 1. **更多 Provider** — Google(国内需翻墙)、微信(需商户资质) 2. **OAuth 端点管理** — 后台管理 OAuth 配置,支持动态添加 3. **登录日志** — 记录 OAuth 登录历史,用于安全审计 4. **强制绑定** — 首次登录后强制绑定邮箱或手机号 5. **OAuth 链接有效期** — 支持在个人中心查看和解绑 OAuth 账号