先说结论
Odoo 标准 auth_password_policy 并不是一套“企业级密码治理总控台”。
从 /home/ubuntu/odoo-temp/addons/auth_password_policy/models/res_users.py 和 res_config_settings.py 来看,它做的事情非常克制:
- 从系统参数里读取一个最小长度。
- 在用户真正写入新密码时执行校验。
- 不满足就抛出
UserError,阻止密码落库。 - 如果同时装了
auth_password_policy_signup,注册 / 设密页面会把这条规则提示给前端。
所以它解决的不是“密码制度全家桶”,而是一个更基础但很关键的问题:无论密码来自后台改密、邀请设密、忘记密码重置,最终都要经过同一条底线校验。
真正被强制的,其实只有“最小长度”
很多人第一次看到“Password Policy”这个名字,会自然脑补出:
- 至少有大小写 / 数字 / 特殊字符;
- 最好带历史密码防复用;
- 应该支持过期轮换;
- 也许还能按用户组定不同策略。
但标准源码里,get_password_policy() 返回的只有一个键:minlength。
而 _check_password_policy() 也只做一件事:遍历待写入的密码,检查 len(password) < minlength。如果不满足,就报错:
Your password must contain at least X characters...
这意味着一个非常重要的现实:
这个模块的本质不是“复杂度引擎”,而是“统一下限闸门”
它的价值不在于规则多,而在于入口统一。
只要最终调用到了 res.users._set_password(),密码就要过这一关。对实施团队来说,这比“看上去规则很多、实际上各入口不一致”更可靠。
为什么它把校验挂在 _set_password() 上?
这是这个模块设计最聪明的地方。
源码里不是在某个控制器里临时判断,也不是只在后台设置页做一个表单校验,而是重写了:
_set_password()
先跑 _check_password_policy(),再调用父类真正落库。
这样做的意义是:
- 后台管理员改密码,要检查;
- 用户自己重置密码,要检查;
- 邀请用户首次设密码,要检查;
- 任何未来复用
_set_password()的标准流程,也会检查。
换句话说,Odoo 这里守的是模型层边界,不是某个界面边界。
这能避免一个很常见的问题:
前端页面明明提示“至少 12 位”,但后台导入、脚本改密、邀请设密却绕过去了。
标准模块恰恰是在避免这种“提示有了,制度没真正落地”的错觉。
配置参数为什么只放在 ir.config_parameter
res_config_settings.py 里把 minlength 绑定到了:
auth_password_policy.minlength
并且在 onchange 里做了一个很朴素的修正:
self.minlength = max(0, self.minlength or 0)
这说明两个设计意图:
1. 这是全局策略,不是按用户逐个配
它不是写在 res.users 上,也不是按公司 / 组拆开,而是一个全局参数。标准模块默认假设:
- 你需要的是一条数据库级底线;
- 而不是非常复杂的密码分层治理。
2. 0 不是异常值,而是“关闭限制”
很多系统会把 0 当非法配置;Odoo 这里明确把它视为禁用最小长度限制。
所以如果你上线后发现规则没生效,不一定是 bug,也可能只是参数被设成了 0。
为什么还要有 auth_password_policy_signup
如果只看后端逻辑,你会问:
既然模型层已经会拦,为什么还要一个 signup 扩展?
答案是:后端拦截只能保证一致性,前端提示才能保证体验。
auth_password_policy_signup 是一个自动安装的小桥接模块,依赖:
auth_password_policyauth_signup
它把两件事连起来:
- 注册 / 设密页能提前展示密码规则;
- 前端资源里能复用密码强度提示和 policy 信息。
这很重要,因为“统一规则”和“降低挫败感”不是一回事。
如果没有前端联动,用户常见体验会变成:
- 填完整个注册表单;
- 点击提交;
- 最后才看到密码太短;
- 然后重新输入一遍。
而有了前端提示,用户至少在输入时就知道底线在哪里。
邀请设密、忘记密码、公开注册,其实是三条不同链路
这也是很多团队容易混淆的点。
在 Odoo 里,“密码从哪里来”至少有三种典型入口:
- 管理员创建用户并发邀请链接;
- 用户点击忘记密码,收到 reset link;
- 开放 B2C signup,陌生访客自己注册。
这三条链路最终都可能走到 signup() / do_signup() / _set_password() 相关逻辑,但它们前面的身份前提完全不同:
- 邀请设密,重点是“你是不是被邀请的人”;
- 忘记密码,重点是“你是不是这个账号的邮箱拥有者”;
- 公开注册,重点是“系统是否允许未受邀开户”。
而密码策略模块只管最后一层:
你最终想写进去的这个密码,是否满足最低要求。
所以别把它误会成“整个账户安全策略”。它只是安全链路里的最后一道口令质量校验。
这个模块解决了什么,又没有解决什么?
它真正解决了三件事
1. 统一入口
不管谁发起改密,只要走标准 _set_password(),规则一致。
2. 降低配置分裂
管理员不需要在后台、注册页、重置页分别维护不同规则。
3. 提供前端可解释性
通过 signup 扩展,规则不再只是后端报错,而是能被用户提前感知。
但它没有解决这些事
1. 它不做复杂度组合判断
标准模块不要求大小写、数字、符号组合。
2. 它不做密码历史比对
没有“最近 N 次密码不能重复”的逻辑。
3. 它不做密码过期治理
没有“90 天必须改密”的时钟。
4. 它不做风险自适应认证
也不会因为异常设备、异常 IP、异常时间就强制附加验证。
也就是说,它是“密码底线模块”,不是“账号安全总方案”。
实施里最容易犯的误区
误区一:把模块名当成功能清单
看到 password_policy 就自动以为“都管了”。
其实源码常常比名字更诚实。标准 Odoo 在这里刻意保持简单,是为了让最基础的密码质量校验先稳定落地。
误区二:只做前端校验,不做模型层约束
很多自定义会在注册页 JS 里写一堆复杂判断,却忘了后台改密和 API 入口。这样一来,规则只是“看起来存在”。
标准模块反而给了更正确的优先级:先守模型层,再补前端体验。
误区三:把最小长度调得很高,却没评估邀请链路转化
比如一下子从 8 提到 20,理论上更安全,但也会明显增加:
- 首次设密失败率;
- 手机端输入负担;
- 用户保存到不安全记事本的概率。
密码策略不是越硬越好,而是要在安全与可用之间找到可执行平衡。
更实际的配置建议
如果你在 Odoo 里启用这套标准密码策略,我建议这样理解它:
1. 把它当“最低统一标准”
例如先定一个清晰、可执行的最小长度,而不是试图在第一步就实现完整 IAM。
2. 明确哪些入口会产生密码
至少梳理:
- 后台创建用户;
- 邀请设密;
- 忘记密码;
- 是否开放公开注册。
确保这些入口都在你的预期里。
3. 如果要更复杂策略,做增量扩展
例如:
- 在
_check_password_policy()上继续扩展; - 或在独立模块里叠加复杂度、黑名单、历史密码校验;
- 但不要破坏现有统一入口。
4. 把用户提示做在失败之前
既然标准模块已经把最关键的后端边界守住了,前端就应该尽量减少“填完才知道错”的体验。
最后一句
Odoo 的 auth_password_policy 看起来“功能不多”,但它其实抓住了密码治理里最容易被忽略的一点:
真正重要的不是规则写得多华丽,而是所有设密入口最后都落到同一条真实生效的校验线上。
如果你把它当成“密码治理的统一底线”,它很可靠;如果你把它误当成“完整安全体系”,那就会高估它。
这,就是它最值得看懂的边界。
DISCUSSION
评论区