Passkey

Odoo Passkey 为什么不是“换个登录按钮”而已:WebAuthn 登录、身份确认与会话边界讲透

很多人把 Passkey 理解成“无密码登录”,但 Odoo 的 auth_passkey 源码表明,它真正处理的是凭证发现、身份确认、签名计数和会话失效边界。本文用通俗语言讲透这条链路。

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

先说结论

Odoo 的 Passkey 不是“把密码框换成生物识别图标”这么简单。

/home/ubuntu/odoo-temp/addons/auth_passkey/models/res_users.pycontrollers/main.py 能看出,官方真正关心的是四件事:

  • 浏览器拿来的 WebAuthn 凭证到底对应哪个用户
  • 当前登录是在做“首次发现用户”还是“验证已登录用户”
  • 签名计数 sign_count 能不能帮助识别可疑重放或克隆设备
  • 用户新增或删除 Passkey 后,旧会话是否还应该继续信任

所以最短结论是:

Passkey 在 Odoo 里不是一个 UI 功能,而是一条完整的认证链路。


为什么 Odoo 要先“通过凭证找登录名”

_login() 的一个关键动作是:

  • 如果凭证类型是 webauthn
  • 先从 auth_passkey_key 里按 credential_identifier 找到对应用户
  • 再把 credential['login'] 补进去,继续走后面的登录流程

这一步很重要,因为 Passkey 登录和传统用户名密码登录的起点不同。

传统登录通常是:

  • 你先说“我是谁”
  • 系统再验证“你是不是这个人”

Passkey 更像:

  • 你先拿出一个浏览器 / 设备签出来的凭证
  • 系统反过来判断“这个凭证属于谁”

这就是为什么源码里不是先依赖用户输入账号,而是先拿凭证 ID 去查用户。

这对业务理解意味着什么

很多团队以为 Passkey 的价值只在“免输密码”。

其实从系统实现看,它的价值更接近:

把“声明身份”从手填账号,改成浏览器提交的受保护凭证。

也因此,真正关键的是凭证注册、凭证归属和后续验证,而不是页面上那一个按钮。


为什么新增 Passkey 前要走 identity check

action_create_passkey() 上,Odoo 明确用了 @check_identity

这说明系统并不把“当前会话已经登录”视为足够安全的前提,而是把“新增一个长期可用的登录凭证”当成高风险动作。

通俗说就是:

  • 普通浏览页面,当前登录态可能够用
  • 但如果你要给账号再绑一把新钥匙,系统希望你再次证明自己

这背后的安全思想非常朴素:

登录态只证明你现在像是这个人;身份确认则是要求你在高敏感操作前再次证明你真的是这个人。

这一点和很多平台在改密码、关 2FA、绑定新设备时要求二次确认,本质上一样。


_check_credentials() 到底做了什么

当凭证类型是 webauthn 时,Odoo 的核心校验链是:

  1. 解析浏览器传来的 webauthn_response
  2. 根据当前用户和 credential_identifier 找到那条 Passkey 记录
  3. 调用 _verify_auth() 验证签名
  4. 比对并更新 sign_count
  5. 返回认证结果,并明确写上: - auth_method: 'passkey' - mfa: 'skip'

这里最值得注意的是最后一项:mfa: 'skip'

这不等于 Odoo 不重视 MFA

相反,这表示:

  • Odoo 把 Passkey 视为已经足够强的认证手段
  • 它不是“先过一层弱认证,再补一层 MFA”
  • 而是直接把 Passkey 当成可以完成强认证的入口

这也是为什么 Passkey 的产品定位经常被称为“密码替代”,而不是“密码附件”。


sign_count 为什么是这条链路里最容易被忽略的细节

源码里验证成功后,会把新的 sign_count 写回去。

很多人第一次看会疑惑:

  • 为什么每次登录还要更新一个计数器?

因为在 WebAuthn 模型里,这个计数器有助于发现异常情况。

你可以把它理解成:

  • 设备每次成功用这把钥匙签名,都会让计数向前走
  • 如果系统发现计数异常,就会怀疑是不是出现了凭证复制、设备克隆或认证上下文异常

它当然不是万能防线,但它属于那种“平时看不见,出事时很值钱”的追踪字段。

很多安全设计都不是靠一个大而全的万能判断,而是靠几个小而稳的边界共同收口。sign_count 就属于这一类。


为什么“删除 / 新增 Passkey”会影响旧会话可信度

_get_session_token_fields()_get_session_token_query_params()auth_passkey_key_ids 纳入了 session token 的计算依赖。

这意味着:

  • 用户账号下的 Passkey 集合变了
  • 会话签名依赖也会变
  • 原本建立在旧凭证集合之上的 session token 会失去原先的稳定前提

这一步非常关键,因为它解决的是一个常见盲区:

如果账号的认证因子集合都变了,为什么旧会话还能继续被原样信任?

Odoo 的回答是:

  • 不应该。

也就是说,Passkey 不只是登录入口层面的增强,它还被纳入了会话有效性模型。

这比“只要能登录就行”要成熟得多。


/auth/passkey/start-auth 这条路由说明了什么

控制器里暴露了 /auth/passkey/start-auth,返回 _start_auth() 生成的认证参数。

这反映出一个很典型的 WebAuthn 结构:

  • 浏览器不能凭空发起一次可信认证
  • 服务端必须先生成挑战参数
  • 浏览器 / 系统安全模块再基于挑战完成签名
  • 服务端最后验回来的响应

所以 Passkey 从来都不是“前端自己搞定”的功能。

它一定是:

  • 服务端挑战
  • 客户端签名
  • 服务端验证

三段式闭环。

如果团队只盯着前端按钮和浏览器弹窗,往往就会低估后端这段挑战—验证链的安全价值。


新手最容易误解的 4 件事

1. 误以为 Passkey 只是“无密码体验优化”

体验当然更好,但源码重点明显在认证强度和会话边界,不只是少输一次密码。

2. 误以为已登录用户新增 Passkey 不算敏感操作

@check_identity 已经明确告诉你:这就是敏感操作。

3. 误以为 Passkey 登录只需要前端适配

没有服务端挑战、凭证归属查询、签名验证和 session 失效设计,这件事根本不完整。

4. 误以为 Passkey 上线后,账号治理逻辑不用变

恰恰相反。

你需要重新思考:

  • 凭证丢失怎么办
  • 用户换设备怎么办
  • 管理员撤销凭证后旧会话怎么办
  • Portal 用户是否也允许同等能力

这些都是平台治理问题,不只是登录页问题。


实战排错顺序

如果用户反馈“Passkey 能注册但登录异常”或“删掉凭证后会话表现奇怪”,建议按这个顺序查:

  1. 先确认 credential_identifier 是否能唯一找到那条 Passkey 记录
  2. 再看当前操作是登录认证,还是已登录状态下新增凭证
  3. 检查 _verify_auth() 是否因签名验证失败抛出异常
  4. 确认 sign_count 是否出现异常回退或不更新
  5. 检查用户增删 Passkey 后,相关 session token 是否按预期失效

这套顺序的价值在于:

  • 先查身份归属
  • 再查签名校验
  • 最后查会话连带影响

不要一上来只盯浏览器弹窗。


一句话记忆

Odoo 的 Passkey 不是“换掉密码框”,而是把用户发现、身份确认、签名验证、计数追踪和会话失效绑成了一条完整认证链。

DISCUSSION

评论区

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