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.
 
 
 
 

14 KiB

OAuth2 流程图详解

创建日期:2026-05-26


一、OAuth2 登录流程(时序图)

1.1 新用户 OAuth 登录完整流程

┌──────────────────────────────────────────────────────────────────┐
│                    GitHub OAuth 新用户登录流程                    │
└──────────────────────────────────────────────────────────────────┘

  用户                    前端                    后端                    GitHub
   │                       │                       │                       │
   │  1. 点击 GitHub 登录   │                       │                       │
   │──────────────────────>│                       │                       │
   │                       │                       │                       │
   │                       │  2. GET /api/auth/oauth/github/authorize    │
   │                       │────────────────────────────────────────────>
   │                       │                       │                       │
   │                       │                       │  3. 生成 state        │
   │                       │                       │  4. 存储 state        │
   │                       │                       │  5. 拼接授权 URL      │
   │                       │                       │                       │
   │<──────────────────────│  6. 302 重定向        │                       │
   │                       │                       │                       │
   │  7. GitHub 授权页面    │                       │                       │
   │───────────────────────│───────────────────────│──────────────────────>
   │                       │                       │                       │
   │  8. 用户确认授权       │                       │                       │
   │───────────────────────│───────────────────────│──────────────────────>
   │                       │                       │                       │
   │                       │                       │  9. GitHub 回调       │
   │                       │                       │  ?code=xxx&state=yyy  │
   │                       │                       │<──────────────────────│
   │                       │                       │                       │
   │                       │                       │  10. 验证 state       │
   │                       │                       │  11. 用 code 换 token │
   │                       │                       │  12. 获取用户信息      │
   │                       │                       │──────────────────────>
   │                       │                       │                       │
   │                       │                       │  13. 创建新用户       │
   │                       │                       │  14. 创建 session     │
   │                       │                       │  15. 插入 oauth_accounts │
   │                       │                       │                       │
   │<──────────────────────│  16. 302 重定向      │                       │
   │                       │  /auth/login?oauth_success=1               │
   │                       │                       │                       │
   │  17. 检测 URL 参数    │                       │                       │
   │──────────────────────>│                       │                       │
   │                       │                       │                       │
   │                       │  18. 刷新 session    │                       │
   │                       │  /api/auth/session   │                       │
   │                       │────────────────────────────────────────────>
   │                       │                       │                       │
   │  19. 登录成功         │                       │                       │
   │                       │                       │                       │

1.2 关键步骤说明

步骤 操作 说明
3-5 生成 state 防止 CSRF 攻击,state 包含 providerName、redirectUri、userId(可选)、isBinding
9-12 Token 交换 用授权码 code 向 GitHub 换取 access_token,再用 token 获取用户信息
13-15 用户创建 新用户自动注册(用户名 = github_{providerUserId}),创建 session,绑定 OAuth 账号

二、账号绑定流程

2.1 已登录用户绑定 GitHub

┌──────────────────────────────────────────────────────────────────┐
│                     账号绑定流程(已登录用户)                      │
└──────────────────────────────────────────────────────────────────┘

  已登录用户               前端                    后端                    GitHub
   │                       │                       │                       │
   │  1. 点击"绑定 GitHub" │                       │                       │
   │──────────────────────>│                       │                       │
   │                       │                       │                       │
   │                       │  2. GET /api/auth/oauth/github/authorize?bind=1│
   │                       │────────────────────────────────────────────>
   │                       │                       │                       │
   │                       │                       │  3. 生成 binding state │
   │                       │                       │  包含 userId          │
   │                       │                       │                       │
   │<──────────────────────│  4. 302 重定向        │                       │
   │                       │                       │                       │
   │  5. GitHub 授权页面    │                       │                       │
   │───────────────────────│───────────────────────│──────────────────────>
   │                       │                       │                       │
   │  6. 用户确认授权       │                       │                       │
   │───────────────────────│───────────────────────│──────────────────────>
   │                       │                       │                       │
   │                       │                       │  7. GitHub 回调       │
   │                       │                       │<──────────────────────│
   │                       │                       │                       │
   │                       │                       │  8. 验证 binding state│
   │                       │                       │  9. 用 code 换 token  │
   │                       │                       │  10. 获取用户信息      │
   │                       │                       │                       │
   │                       │                       │  11. 插入 oauth_accounts │
   │                       │                       │                       │
   │<──────────────────────│  12. 302 重定向      │                       │
   │                       │  /profile?bind_success=1                   │
   │                       │                       │                       │
   │  13. 显示绑定成功     │                       │                       │
   │                       │                       │                       │

2.2 解绑流程

用户点击"解绑 GitHub"
    ↓
前端 POST /api/auth/oauth/github/unbind
    ↓
后端验证用户登录状态
    ↓
后端从 oauth_accounts 表删除记录
    ↓
返回 success
    ↓
前端刷新绑定列表

三、State 生命周期管理

3.1 State 状态转换

┌──────────────────────────────────────────────────────────────────┐
│                       State 生命周期                               │
└──────────────────────────────────────────────────────────────────┘

  1. generate()           2. validate()         3. consume()
┌─────────────┐          ┌─────────────┐          ┌─────────────┐
│   生成      │─────────>│   验证      │─────────>│   消费      │
│  新 state   │          │  检查 TTL   │          │  删除 state │
└─────────────┘          └─────────────┘          └─────────────┘
                             │
                             │ 失败
                             ▼
                       ┌─────────────┐
                       │   拒绝      │
                       │  返回 null  │
                       └─────────────┘

TTL: 5 分钟(300000ms)

3.2 State 数据结构

type OAuthState = {
  providerName: string;    // 'github' | 'google'
  redirectUri: string;     // 回调跳转地址
  createdAt: number;      // timestamp
  userId?: number;        // 绑定模式下才有值
  isBinding: boolean;    // 是否为绑定模式
};

四、错误处理流程

4.1 错误分类与处理

错误码 场景 用户反馈
OAUTH_STATE_INVALID state 被篡改或不存在 "授权失败,请重试"
OAUTH_STATE_EXPIRED state 超过 5 分钟 "授权已过期,请重新授权"
OAUTH_TOKEN_EXCHANGE_FAILED token 交换失败 "登录失败,请重试"
OAUTH_USER_INFO_FAILED 获取用户信息失败 "获取用户信息失败"
OAUTH_ALREADY_BIND 账号已绑定其他用户 "该 GitHub 账号已绑定其他用户"

4.2 错误流程图

GitHub 回调
    │
    ▼
验证 state ──失败──> 重定向 /auth/login?oauth_error=OAUTH_STATE_INVALID
    │
    成功
    │
    ▼
交换 token ──失败──> 重定向 /auth/login?oauth_error=OAUTH_TOKEN_EXCHANGE_FAILED
    │
    成功
    │
    ▼
获取用户信息 ──失败──> 重定向 /auth/login?oauth_error=OAUTH_USER_INFO_FAILED
    │
    成功
    │
    ▼
查找 oauth_accounts
    │
    ├── 已存在且 userId 匹配 ──> 创建 session ──> 重定向 /auth/login?oauth_success=1
    │
    ├── 已存在但 userId 不匹配 ──> 重定向 /auth/login?oauth_error=OAUTH_ALREADY_BIND
    │
    └── 不存在(绑定模式)──> 插入 oauth_accounts ──> 重定向 /profile?bind_success=1
    │
    └── 不存在(登录模式)──> 创建新用户 ──> 插入 oauth_accounts ──> 创建 session
                                    │
                                    ▼
                              重定向 /auth/login?oauth_success=1

五、安全性说明

5.1 CSRF 防护

  • 每次授权请求生成随机 UUID 作为 state
  • state 存储在服务端内存中,5 分钟 TTL
  • 回调时验证 state 是否匹配,防止被篡改

5.2 Token 安全

  • access_token 只在后端流转,不暴露给浏览器
  • 回调 URL 直接重定向到前端,不经过浏览器

5.3 会话安全

  • OAuth 登录成功后创建新的 session
  • 使用 HttpOnly Cookie 存储 session ID

六、数据库操作

6.1 oauth_accounts 表操作

插入(用户首次 OAuth 登录或绑定):
INSERT INTO oauth_accounts (user_id, provider, provider_user_id, username, email, avatar, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)

查询(根据 provider + provider_userId 查找):
SELECT * FROM oauth_accounts WHERE provider = ? AND provider_user_id = ?

查询(根据 userId 查找所有绑定):
SELECT * FROM oauth_accounts WHERE user_id = ?

删除(解绑):
DELETE FROM oauth_accounts WHERE user_id = ? AND provider = ?

6.2 唯一性约束

  • UNIQUE(provider, provider_user_id) 确保同一 Provider 不能重复绑定
  • FOREIGN KEY (user_id) REFERENCES users(id) 确保用户删除时级联删除 OAuth 绑定