You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

22 KiB

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 接口

// 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<string, unknown>): OAuthUserInfo;
}

export type OAuthUserInfo = {
  provider: string;
  providerUserId: string;          // 第三方平台的用户 ID
  username?: string;
  email?: string;
  avatar?: string;
};

2.2 OAuth 模块核心

// packages/oauth/src/core/oauth-manager.ts
export class OAuthManager {
  // 注册 Provider
  registerProvider(provider: OAuthProvider): void;

  // 生成授权 URL(含 state)
  async getAuthorizationUrl(providerName: string): Promise<string>;

  // 处理回调(交换 token,获取用户信息)
  async handleCallback(providerName: string, code: string, state: string): Promise<OAuthCallbackResult>;

  // 绑定账号(已登录用户绑定第三方账号)
  async bindAccount(userId: number, providerName: string, providerUserId: string): Promise<void>;

  // 解绑账号
  async unbindAccount(userId: number, providerName: string): Promise<void>;

  // 获取用户已绑定的 OAuth 账号列表
  async getUserBindings(userId: number): Promise<OAuthBinding[]>;
}

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)

// packages/oauth/src/core/oauth-state-store.ts
// 内存 Map + 5min TTL 过期清理
// State 包含: providerName, redirectUri, createdAt, userId(可选)

State 存储结构:

type OAuthState = {
  providerName: string;
  redirectUri: string;
  createdAt: number;      // timestamp
  userId?: number;        // 绑定模式下才有值
  isBinding: boolean;     // 是否为绑定模式
};

三、数据库设计

3.1 oauth_accounts 表

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 增加:

<!-- 现有登录表单 -->
<template>
  <div class="login-form">
    <UForm ...> ... </UForm>

    <!-- 新增第三方登录分割线 -->
    <div class="divider">
      <span></span>
    </div>

    <!-- 新增OAuth 登录按钮 -->
    <button @click="loginWithGithub" class="btn-github">
      <Icon name="github" /> GitHub 登录
    </button>
  </div>
</template>

<script setup>
async function loginWithGithub() {
  // 跳转到后端授权接口
  window.location.href = '/api/auth/oauth/github/authorize';
}
</script>

七、模块化扩展(添加 Google Provider)

只需在配置文件中添加:

// 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 绑定用户不匹配 重新登录授权

九、配置项

// .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 账号