第三方账号绑定与最小权限

拿到第三方身份之后

OAuth 流程走完,你的系统拿到了第三方用户信息(比如 GitHub 的 id、login、name)。但登录还没结束——你还需要决定:这个外部身份对应本地哪个账号。

这就是账号绑定要解决的问题。

绑定策略

第一次第三方登录时,通常有三种选择:

策略做法适用
自动建号直接创建本地账号并绑定简洁,但可能产生僵尸账号
绑定引导若邮箱已存在则要求验证后绑定更安全,防账号串绑
合并选择让用户选择「新建」或「绑定已有」灵活,体验稍重

核心原则:openid 不是主键

不要把外部平台的 openid 当成你系统的内部主键。

原因很实际:

  • 用户可能绑定多个身份来源(GitHub + 微信 + Google)。
  • 外部平台可能删号、改 ID、撤销授权。
  • 你的用户数据要跟随你自己的 user_id,而不是外部 ID。

正确的模型是:

user_account(你自己的用户表)
  └─> identity_binding
        ├─ provider: github
        ├─ external_sub: <github 的用户 id>
        └─ user_id: <你的本地用户 id>

identity_binding 只做「外部身份 → 本地用户」的映射,一行一个绑定来源。

绑定要回答的问题

  • 第一次第三方登录,自动建号还是引导绑定?
  • 如果邮箱已存在,是否允许直接绑定?(直接绑可能被「先注册同邮箱 → 再引导绑定」攻击,通常要验证邮箱归属)
  • 用户如何解绑第三方账号?
  • 第三方账号被撤销授权后,本地会话如何处理?(降级、要求重新验证)

Scope 与最小权限

OAuth 申请权限时,一个原则贯穿始终:scope 最小化。

  • 如果只是登录,不要申请读写仓库、联系人、支付等权限。
  • 申请越多,用户越不信任,泄露后风险也越大。
❌ scope=repo,user,notifications,admin:org
✅ scope=read:user    (只读用户公开信息)

第三方 access token 要加密存储,并记录来源、过期时间和刷新方式。如果只是登录用途,通常不需要长期保存第三方 token——用完即可,需要时再重新授权。

小结

  • 本地系统保留自己的 user_id,外部 openid 只做映射。
  • 绑定策略要防「邮箱串绑」攻击。
  • scope 最小化,第三方 token 按需保存、加密存储。
  • 解绑、撤销授权都要有对应的本地处理。

到这里,登录、Token、会话、SSO、OAuth 都齐了。下一章加最后一块安全拼图:多因素认证 MFA。

发现内容有问题?

告诉我们哪里需要改进。