第三方 逻辑分析
目标函数:controller/oauth.go:131
这个函数处理的是 OAuth 账号绑定流程,不是 OAuth 登录注册流程。它只会在用户已经登录的情况下执行:主入口 controller/oauth.go:43 会先校
验 state,然后判断 session 里是否已有 username,如果有,就进入 handleOAuthBind。
触发条件
入口逻辑在 HandleOAuth 中:
根据路由参数 provider 获取 OAuth provider。
校验 OAuth state,用于防 CSRF。
如果当前 session 中存在 username,说明用户已登录。
已登录时不走登录/注册,而是调用:
handleOAuthBind(c, provider)
所以 handleOAuthBind 的语义是:
当前登录用户把第三方 OAuth 账号绑定到自己的系统账号。
执行流程
1. 检查 Provider 是否启用
如果当前 OAuth provider 被关闭,直接返回错误。
这里虽然主流程后面也会检查 provider enabled,但绑定分支提前 return 了,所以绑定函数里必须自己再检查一次。
2. 用授权码换 Token
从回调 URL 中读取 code,调用 provider 的 ExchangeToken 换取访问令牌。
如果失败,进入统一 OAuth 错误处理:
3. 用 Token 获取 OAuth 用户信息
返回的核心字段是 oauthUser.ProviderUserID,用于标识第三方平台上的用户 ID。
例如 GitHub 当前逻辑中,ProviderUserID 是 GitHub numeric id,同时 Extra[“legacy_id”] 保存旧逻辑使用的 GitHub login。
4. 检查该 OAuth 账号是否已被绑定
位置:controller/oauth.go:153
如果第三方账号 ID 已经存在绑定关系,拒绝绑定。
不同 provider 的检查方式不同:
内置 provider:通常查 users 表上的字段,比如 github_id、discord_id、oidc_id。
自定义 OAuth provider:查 user_oauth_bindings 表。
5. 检查 legacy_id,避免迁移期重复绑定
这是为了兼容历史数据。
典型场景是 GitHub:
老版本可能用 GitHub login 作为绑定 ID。
新版本改成 GitHub numeric id。
为避免同一个 GitHub 账号在迁移期间被重复绑定,会同时检查新 ID 和旧 ID。
6. 从 Session 获取当前登录用户
这里依赖 session 中存在 id,并且类型必须是 int。
如果查不到用户或数据库错误,返回 common.ApiError。
潜在风险:这里直接做了 id.(int) 类型断言。如果 session 异常、缺失或类型不是 int,会 panic。不过正常路径下,登录时 controller/
user.go:97 会写入 id。7. 根据 Provider 类型执行绑定
位置:controller/oauth.go:176
这里分两类处理。
自定义 OAuth Provider
自定义 provider 不写 users 表字段,而是写 user_oauth_bindings 表。
底层逻辑在 model/user_oauth_binding.go:108:
检查 provider_id + provider_user_id 是否已经绑定给其他用户。
如果当前用户没有该 provider 的绑定,则创建。
如果当前用户已有该 provider 的绑定,则更新 provider_user_id。
也就是说,自定义 OAuth 支持“重新绑定同一个 provider 下的另一个第三方账号”。
内置 OAuth Provider
8. 返回绑定成功
整体流程图
OAuth callback
|
v
HandleOAuth
|
v
校验 provider 是否存在
|
v
校验 state
|
v
session.username 存在?
|
+-- 否 --> OAuth 登录/注册流程
|
+-- 是 --> handleOAuthBind
|
v
provider 是否启用?
|
v
code 换 token
|
v
token 拉取 OAuth 用户信息
|
v
ProviderUserID 是否已被绑定?
|
v
legacy_id 是否已被绑定?
|
v
读取当前登录用户
|
v
自定义 provider?
|
+-------+--------+
| |
v v
写 user_oauth_bindings 更新 users 表 provider 字段
| |
+-------+--------+
|
v
返回绑定成功
关键业务规则
只有已登录用户才会进入绑定流程。
一个 OAuth 第三方账号不能绑定到多个系统用户。
自定义 OAuth provider 的绑定关系存储在 user_oauth_bindings。
内置 OAuth provider 的绑定关系存储在 users 表对应字段。
GitHub 等 provider 支持 legacy_id 检查,用于防止历史 ID 迁移期间重复绑定。
绑定成功后不会重新登录,也不会刷新 session 用户信息,只返回绑定成功。
需要注意的点
- session.Get(“id”) 直接类型断言为 int,session 异常时可能 panic。
- 自定义 OAuth 的 UpdateUserOAuthBinding 支持覆盖当前用户已有绑定,等价于“重新绑定”。
- 内置 provider 的 user.Update(false) 会更新整个用户模型,具体更新范围取决于 model.User.Update 的实现。
- provider.IsUserIDTaken 和后续写入不是一个事务,理论上存在并发重复绑定竞争。不过数据库唯一索引或字段唯一约束是否兜底,需要看具体
provider 对应字段和表结构。