先说结论
auth_totp_mail 不是“把手机验证码换成邮箱验证码”的轻量版 2FA。
从 models/res_users.py 和 controllers/home.py 看,Odoo 给它的定位更接近:
- 当组织要求 MFA,但用户不一定有 authenticator app 时的兜底方案
- 一个带明显安全边界的过渡型第二因子
这也是为什么源码一边提供邮箱 OTP,一边又在很多地方强调:
- 登录仍停在
pre_uid预认证阶段 - 发码与验码分别限流
- 代码与
login_date绑定 - 新设备登录还要额外提醒
一句话概括:
邮件 OTP 在 Odoo 里不是“更方便的 TOTP”,而是“安全性更弱、但比没有 MFA 强得多”的受控方案。
web_totp() 为什么 insist 在 pre-auth 阶段发码
controllers/home.py 里的 web_totp() 很关键。它先走父类逻辑,然后检查两件事:
request.session.get('pre_uid')必须存在request.session.uid不能已经建立
也就是说,用户此时已经过了第一步身份识别,但还没有拿到真正登录态。
这条边界非常重要,因为它把邮件 OTP 放在了一个明确的中间状态:
- 不是匿名用户随便要验证码
- 也不是已登录用户再来补一个形式上的确认
而是:
- 用户先通过用户名密码或其他主身份校验
- 系统只暂存“预认证用户是谁”
- 第二因子通过后,才升级成正式会话
如果没有这层 pre-auth 设计,邮件 OTP 很容易退化成“一个能被流程绕开的附加步骤”。
_get_totp_mail_key() 为什么要绑 login_date
_get_totp_mail_key() 用 HMAC 生成 key,输入里有:
- 用户 ID
- login
login_date
这意味着同一个用户,不是永远复用同一个邮箱 OTP 派生 key。
最值得注意的是 login_date:它让验证码派生结果与当前这次登录上下文挂钩,而不是跟账号永久绑定。
这样做的好处是:
- 上一轮登录里发出去的码,不适合长期有效
- 用户成功登录、
login_date更新后,旧派生上下文自然失效 - 验证码更像“这次挑战的邮件确认”,而不是账号静态秘钥的替代品
这也解释了为什么官方不把邮件 OTP 当成标准 TOTP App:它追求的不是离线持续生成,而是跟登录事务绑定的短期挑战。
为什么验证码窗口长达 1 小时,却仍然要限流
_get_totp_mail_code() 和 _check_credentials() 里,匹配窗口是 window=3600, timestep=3600,也就是按小时切片。
很多人看到这里会担心:“一小时是不是太长了?”
单看时长,确实比常见 30 秒 TOTP 宽很多。但源码不是只靠时间窗口防守,它还叠了另外两层:
- 发送限流:
_totp_rate_limit('send_email') - 校验限流:
_totp_rate_limit('code_check')
这意味着攻击者不能无限刷发码,也不能无限试 6 位码。
Odoo 在这里做的是一种现实主义权衡:
- 邮件投递有天然延迟
- 用户不一定盯着邮箱实时刷新
- 太短的窗口会把大量正常用户打成失败
所以官方宁可把单码可用时间放宽,也要靠限流、登录上下文绑定、预认证阶段和设备信任来缩小整体风险面。
_send_totp_mail_code() 透露了什么产品意图
这个方法不只是发邮件。它还会把以下环境信息放进模板上下文:
- device
- browser
- ip
- geoip location(如果可得)
这说明 Odoo 并没有把这封邮件当成“单纯送一个 6 位码”。它更像一封安全事件通知 + 验证邮件。
换句话说,官方知道邮箱 OTP 的核心短板是:
- 邮箱本身可能已被他人访问
- 用户可能不知道这次登录是不是自己发起的
所以它把设备、浏览器、IP、地点等上下文一起放进去,让用户有机会识别异常。
这不是装饰信息,而是在补邮箱 OTP 天生比 app TOTP 更弱的那部分安全感知。
受信设备提醒为什么和邮件 OTP 绑定得这么紧
authenticate() 之后,模块还会调用 _notify_security_new_connection()。
逻辑是:
- 如果用户启用了 MFA
- 且当前
td_idcookie 对应的浏览器不是 trusted device - 那么系统发送“新设备登录提醒”
这套设计说明,Odoo 不把邮件 OTP 视为一次性问题,而把它放进一套持续登录治理中。
因为真实风险不是“这次能不能输对码”,而是:
- 这个浏览器以后要不要被当成熟设备
- 用户是否能意识到自己的账号刚刚被一个新终端使用过
所以邮件 OTP 真正的配套对象,不只是验证码本身,而是:
- trusted device cookie
- 新设备提醒
- security setting update 通知
邮件 OTP 最适合什么场景,不适合什么场景
适合
- 企业强制所有内部员工开 MFA,但不能要求每个人都装 app
- 推进 MFA 的过渡阶段,需要比“纯密码”更强、比硬件密钥更低门槛的方案
- 希望先把大量账号纳入基本二次验证
不适合
- 高敏感后台或高权限管理员长期只依赖邮箱 OTP
- 把邮箱 OTP 当成和 Authenticator App 同级的长期标准
- 邮箱安全本身比较脆弱、多人共用邮箱、或邮件转发不可控的环境
如果你必须对业务说人话,我会这么总结:
邮件 OTP 适合“先把所有人带上车”,不适合“把最关键的人永远留在这辆车上”。
实战最容易犯的 5 个误区
误区 1:把邮件 OTP 当成“无手机版 TOTP”
两者外观像,但风险模型完全不同。邮件链路有投递、收件箱、客户端同步等额外暴露面。
误区 2:只关心验证码时效,不关心限流
没有发码和验码限流,再短的时效也可能被撞库和暴力尝试放大风险。
误区 3:跳过 pre-auth 设计
如果你让匿名态或已登录态都能随意拉验证码,流程边界会立刻模糊。
误区 4:不附带环境信息
设备、浏览器、IP、地点不是锦上添花,而是帮助用户识别异常登录的重要信号。
误区 5:高权限用户长期停留在邮件 OTP
对于财务、管理员、技术运维账户,最好尽快转向 app TOTP、Passkey 或更强认证器。
结语
auth_totp_mail 的源码很诚实:它没有假装自己是最强的第二因子,而是用一整套边界告诉你——这是一种更易部署、但必须配合限流、预认证和设备治理一起使用的 MFA 方案。
也正因为官方把这些边界写得这么清楚,我们在实施时才不该偷懒地把它讲成“邮箱收个码而已”。
真正要上线的是一条认证恢复和过渡治理链路,不是一封邮件。
DISCUSSION
评论区