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
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 绑定