OAuth

Odoo OAuth 登录为什么不只是“跳去第三方再跳回来”:账号映射、自动注册与 token 登录边界讲透

很多团队配置 OAuth 时只盯着回调地址,但 auth_oauth 真正复杂的地方在用户映射、是否允许自动创建账号、access token 如何回填,以及返回后怎样完成会话登录。本文把这条链路拆开讲清楚。

框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

Odoo 的 OAuth 登录,核心从来不只是“浏览器跳去第三方授权,再跳回来”。

/home/ubuntu/odoo-temp/addons/auth_oauth/models/res_users.pycontrollers/main.py 看,真正的主线是:

  • 先验证第三方 access token 是否可信
  • 再把第三方身份统一成 Odoo 可理解的 user_id
  • 决定这个身份是映射到已有用户,还是触发自动注册
  • 再把 access token 与本地用户关系写回
  • 最后才通过 oauth_token 完成 session 登录

所以一句话概括:

OAuth 在 Odoo 里不是“第三方替你登录”,而是“第三方先证明身份,Odoo 再决定本地账号如何承接这份身份”。


list_providers() 说明登录页真正生成了什么

登录页上看到的“用 Google 登录”“用 Microsoft 登录”这类按钮,并不是简单超链接。

list_providers() 会给每个 provider 组一条授权链接,参数里至少包含:

  • response_type=token
  • client_id
  • redirect_uri
  • scope
  • state

其中最关键的是 state

因为 state 里不只是“防伪随机串”这么简单,在 Odoo 里它还带着:

  • 数据库 d
  • provider id p
  • 登录后要去哪里 r
  • 邀请 / 注册 token t
  • 以及必要上下文 c

这说明 Odoo 把 OAuth 回调理解成一次“带上下文的重入”。

它不是回来就完事,而是回来之后还得知道:

  • 你是哪个数据库
  • 你是为哪个 provider 回来的
  • 你是普通登录、受邀注册,还是限制不允许创建用户
  • 最终要跳回哪一页

_auth_oauth_validate() 为什么先做“身份标准化”

不同 OAuth 提供商给的字段不完全一致。

有的给:

  • sub

有的给:

  • id

有的给:

  • user_id

Odoo 在 _auth_oauth_validate() 里做了一件非常务实的事:

  • 先请求 validation endpoint
  • 如果 provider 还配置了 data endpoint,再把用户资料补齐
  • 然后从 sub / id / user_id 里抽出一个统一主体身份
  • 最后统一写成 validation['user_id']

这一步的意义在于:

别让每个 provider 都把后续登录链路搞成不同形状。

平台层最怕“后面几十处逻辑都要知道每家第三方字段叫啥”。

Odoo 在这里把差异尽量收敛在验证阶段,后面统一拿 user_id 继续走。

这就是典型的平台抽象价值。


为什么唯一约束落在 (oauth_provider_id, oauth_uid)

源码里有约束:

  • 同一个 provider 下的 oauth_uid 必须唯一

这非常合理,因为第三方身份不能只看 oauth_uid 本身。

举个简单例子:

  • Google 上的 123
  • 某自建 OAuth 服务上的 123

这两个 123 没任何天然可比性。

所以 Odoo 用“provider + uid”联合唯一,而不是只靠 uid。

这听起来平常,但它决定了系统能不能正确理解“同名不同源”的账号。


_auth_oauth_signin() 其实决定了账号接管策略

这个方法是 OAuth 成功后的真正分叉点。

它会先查:

  • 是否已经存在一条 (provider, oauth_uid) 对应的本地用户

如果有:

  • 更新该用户的 oauth_access_token
  • 直接返回本地 login

如果没有:

  • 看上下文里是否禁止创建用户 no_user_creation
  • 若禁止,就返回空,最终触发拒绝
  • 若允许,就用 _generate_signup_values() 生成本地账号字段,并走 signup()

这就把很多实施时的疑问解释清楚了:

为什么有的 OAuth 能第一次直接注册进来,有的却只允许已有用户登录?

因为问题不在 provider 本身,而在:

  • 当前上下文是否允许新建本地用户
  • 当前请求有没有 invitation token
  • 当前数据库是否允许这种注册路径

也就是说,OAuth 只是外部身份入口,最终是否“接纳为本地账号”仍由 Odoo 决定。


state['t'] 为什么很关键

如果登录链路里带了 token,get_state() 会把它写进 state['t']

这通常意味着:

  • OAuth 登录不一定是完全自由注册
  • 它也可能是“带邀请的账号落地”
  • 或“绑定到某个预期注册流程”

所以实施时如果碰到:

  • 第三方授权明明成功了
  • 但 Odoo 还是不给创建账号

别急着怪 provider。

先看是不是:

  • 注册 token 不对
  • 邀请已过期
  • 该流程本来就不允许随便落新用户

为什么登录完成后还要再走 request.session.authenticate()

很多人会以为:

  • 第三方已经验证通过了
  • Odoo 直接建 session 不就行了?

但源码没有这么偷懒。

/auth_oauth/signin 里,Odoo 会构造:

  • {'login': login, 'token': key, 'type': 'oauth_token'}

然后再调用 request.session.authenticate()

这意味着 OAuth 虽然来自外部身份验证,但最终还是要回到 Odoo 自己的会话体系里。

这个设计的好处是:

  • 登录入口可以多样化
  • 但 session 生成和后续权限承接仍走统一机制

平台架构上,这比每条登录方式都自己造一套 session 逻辑稳定得多。


_check_credentials() 为什么允许用 oauth_access_token 认证

_check_credentials() 里有个很有意思的兜底:

  • 如果普通认证失败
  • 且凭证类型是 oauth_token
  • 就会查当前用户保存的 oauth_access_token 是否匹配

如果匹配,返回:

  • auth_method: 'oauth'
  • mfa: 'default'

这说明 OAuth 不只是“授权回调那一下能用”。

它还进入了 Odoo 的凭证判断分支,成为会话建立的一部分。

但这里也提醒我们一个现实问题:

既然 access token 被保存并参与登录,就必须认真考虑撤销、过期和最小暴露面。

否则 OAuth 很容易从“便捷登录”滑向“长期凭证治理混乱”。


新手最容易误解的 5 件事

1. 误以为 OAuth 配通回调地址就结束了

回调地址只是入口,真正复杂的是用户映射和本地账号接管策略。

2. 误以为第三方授权成功就一定能登录成功

如果本地没有合适账号映射,且禁止自动创建,照样会被拒绝。

3. 误以为不同 provider 的用户资料天然同构

Odoo 明明在 _auth_oauth_validate() 里做了统一抽象,这恰好说明它们并不天然一致。

4. 误以为 OAuth 登录后与本地 session 没关系

实际上最后还是要回到 session.authenticate() 这条路。

5. 误以为 access token 只是临时过桥数据

源码直接保存并回写它,说明它已经进入平台认证边界。


实战排错顺序

如果用户反馈“第三方授权成功但 Odoo 登录失败”,建议按这个顺序查:

  1. validation endpoint 和 data endpoint 返回的数据里,是否能抽出稳定的主体身份
  2. (oauth_provider_id, oauth_uid) 是否已映射到本地用户
  3. 当前上下文是否带了 no_user_creation 或 invitation token
  4. signup() 是否因邀请过期、注册限制等原因失败
  5. 回调后 request.session.authenticate() 是否因本地 token 认证失败而中断

这套顺序能把“第三方成功”与“本地落地成功”明确拆开。


一句话记忆

Odoo OAuth 的重点不是“跳转回来”,而是把外部身份标准化、映射到本地账号,并在允许的治理规则下再生成自己的会话。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。