框架深潜

Odoo 密码策略为什么常常不像你想的那样“很完整”:最小长度、注册页提示与重置流程边界讲透

很多人一看到 Odoo 里的 auth_password_policy,就以为它已经覆盖复杂度、历史密码、过期轮换等整套企业密码治理。翻源码会发现,标准模块真正强制的核心只有“最小长度”,而且它还要和 signup、reset password、前端密码提示一起配合,才能形成一条不打架的用户链路。

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

先说结论

Odoo 标准 auth_password_policy 并不是一套“企业级密码治理总控台”。

/home/ubuntu/odoo-temp/addons/auth_password_policy/models/res_users.pyres_config_settings.py 来看,它做的事情非常克制:

  1. 从系统参数里读取一个最小长度。
  2. 在用户真正写入新密码时执行校验。
  3. 不满足就抛出 UserError,阻止密码落库。
  4. 如果同时装了 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_policy
  • auth_signup

它把两件事连起来:

  1. 注册 / 设密页能提前展示密码规则;
  2. 前端资源里能复用密码强度提示和 policy 信息。

这很重要,因为“统一规则”和“降低挫败感”不是一回事。

如果没有前端联动,用户常见体验会变成:

  • 填完整个注册表单;
  • 点击提交;
  • 最后才看到密码太短;
  • 然后重新输入一遍。

而有了前端提示,用户至少在输入时就知道底线在哪里。

邀请设密、忘记密码、公开注册,其实是三条不同链路

这也是很多团队容易混淆的点。

在 Odoo 里,“密码从哪里来”至少有三种典型入口:

  1. 管理员创建用户并发邀请链接
  2. 用户点击忘记密码,收到 reset link
  3. 开放 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

评论区

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