# 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 数据结构 ```typescript 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 绑定