先说结论
Odoo 的 auth_timeout 不是“时间到了就 logout”这一种粗暴策略。
从 /home/ubuntu/odoo-temp/addons/auth_timeout/models/ir_http.py、models/res_users.py 与 controllers/main.py 可以看出,官方实际上搭了两层边界:
- 一层是 会话总寿命,到了就要求重新登录
- 一层是 闲置后的身份复核,不一定踢下线,但要重新证明“现在操作的人还是你”
更进一步,它还能把复核流程拆成:
- 先验第一因子
- 再要求第二因子
- 且第二因子不能和第一因子重复
一句话概括:
Odoo 处理的不是“超时”本身,而是“什么时候该重新确认操作者身份”。
为什么要区分 lock_timeout 和 lock_timeout_inactivity
_must_check_identity() 会同时检查两类阈值:
1. lock_timeout
看的是 session 的 create_time。
意思是:
- 不管你中间有没有操作
- 只要这次会话活得够久
- 就必须重新登录
这更像“整段会话最长能活多久”。
2. lock_timeout_inactivity
看的是 identity-check-next。
意思是:
- 不是你登录多久了
- 而是你是否已经闲置到足够久
- 闲置后再继续操作时,要先做身份复核
这更像“离开座位一段时间后,回来别直接继续干敏感事”。
很多系统只做前者,所以会出现两个问题:
- 用户明明一直在工作,却突然被整段踢出
- 用户离开工位很久,只要会话还没过期,回来的人就能继续点
Odoo 用两套阈值分开处理,正是在平衡体验和安全。
为什么有时是重新登录,有时只是弹出复核框
_authenticate() 在发现需要重新确认身份时,不会一刀切。
它会根据 _must_check_identity() 返回的结果区分:
- 如果是
logout,抛SessionExpiredException - 如果是
check_identity,抛CheckIdentityException
两者后果完全不同。
logout
这意味着原会话已经不值得继续信任了,用户要回到登录页重新建立完整会话。
check_identity
这意味着系统还认可当前会话上下文,但要求在继续前再确认一次身份。
所以它更像:
- 会话没完全死
- 但高信任状态暂时失效
这也是为什么 CheckIdentityException 对 HTTP 请求会被重定向到 /auth-timeout/check-identity,而 JSONRPC 场景会走弹窗式交互。
平台层面看,这不是页面差异,而是 同一安全规则在不同请求类型上的不同承接方式。
为什么 WebSocket 活跃状态也会影响超时判断
_set_session_inactivity() 的注释写得很清楚:
- 前端 JS 会把“用户是否活跃”通过 websocket 发回来
- 关闭最后一个标签页、断网、连接关闭,也会触发状态更新
这很关键。
因为如果系统只看“最后一次 HTTP 请求时间”,会有两个偏差:
- 用户明明还盯着页面、持续操作前端,但未必频繁打后端请求
- 用户其实已经关了页面,却可能没有一条“我走了”的 HTTP 请求
Odoo 通过 WebSocket 把“人在不在场”这件事引入会话判断,做得比传统 session timeout 更细。
简单说:
它不只关心你多久没请求服务器,还关心你是否真的还在这台前端上活动。
为什么复核时还要区分第一因子和第二因子
_check_identity() 里有一段很值得看:
- 先列出当前用户可用的认证方式
_get_auth_methods() - 如果这次复核要求多因子,先成功通过第一因子
- 再把这次已经用过的方式记录到
identity-check-1fa - 第二次复核时,把这个方式从可选列表里移除
这意味着什么?
意味着 Odoo 不接受“同一个因子连用两次假装是多因子”。
例如:
- 第一步已经用 password
- 第二步就不能还是 password
- 如果有 TOTP、totp_mail 或 webauthn,就必须换另一种证明方式
这是真正理解 MFA 的做法。
很多系统只是多弹一个框,但如果第二次还是同一种凭据,本质上安全增益很有限。Odoo 在这里把“因素不同”写进了流程约束里。
为什么前端也能知道闲置阈值
session_info() 和 get_frontend_session_info() 都会把 lock_timeout_inactivity 传给前端。
这意味着前端不是在“猜会不会过期”,而是拿到了明确阈值,可以据此:
- 触发活跃 / 闲置信号
- 提前展示 UI 提示
- 在需要时弹出身份确认交互
所以 auth_timeout 不是纯后端特性,而是一套前后端协同的会话治理机制。
新手最容易误解的 5 件事
1. 误以为超时只有“继续用”或“被登出”两种状态
其实还有“会话还在,但必须重新证明身份”。
2. 误以为用户活跃只看请求日志
源码明确把 websocket 存在感也算进去了。
3. 误以为多因子复核只是多输一次东西
真正关键的是第二次不能重复第一种认证方式。
4. 误以为所有请求遇到超时都该跳登录页
HTTP 页面请求和 JSONRPC 请求在承接方式上不一样。
5. 误以为闲置超时和会话总寿命是同一个参数
前者解决“你离开太久”,后者解决“这次会话活得太久”。
实战排错顺序
如果你碰到“用户没掉线但总被要求重新验证”或“明明在线却忽然弹身份确认”,建议按这个顺序查:
- 先看用户所在组配置的
lock_timeout与lock_timeout_inactivity - 确认触发的是 logout 还是 check_identity
- 检查前端 websocket 心跳 / presence 是否正常上报
- 看
identity-check-next、identity-check-last、identity-check-1fa这些 session 键的变化 - 若启用 MFA,确认第二步有没有被错误地限制成与第一步同一种方式
一句话记忆
Odoo Auth Timeout 的核心不是“超时踢人”,而是把会话寿命、闲置状态和多因子复核揉成一条可分层处理的身份确认链。
DISCUSSION
评论区