密码强度校验应使用thinkphp 6+的validate验证器,而非控制器if判断或模型回调;需在验证器中通过regex规则或自定义方法实现,且必须服务端二次校验。

密码强度校验该用哪个钩子函数?
ThinkPHP 6+ 中,validate 是最直接、最可控的校验入口,别在控制器里手写 if 判断。它天然支持规则链、自定义错误提示,且能与模型/表单自动绑定。
常见错误是把强度逻辑塞进 save() 前的回调(如 beforeWrite),这会导致校验失败时仍可能触发事件或日志,且无法返回结构化错误信息。
- 在模型的
validate属性中声明规则:['password' => 'require|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[!@#$%^&*]).{8,}$/'] - 正则说明:至少含大小写字母、数字、特殊字符,总长 ≥8;注意
regex规则需完整包裹在斜杠中,TP6 默认启用 PCRE 模式 - 若需更灵活控制(如排除常见弱密码),建议单独写验证器类,继承
thinkValidate,重写check方法,在其中调用in_array($value, $weakList)
哈希存储必须绕开 md5 和 sha1
TP6 默认的 thinkacadeHash::make() 底层用的是 password_hash()(PASSWORD_ARGON2ID 或 PASSWORD_DEFAULT),这是唯一推荐方式。手动拼接 salt + md5 不仅过时,而且完全不安全。
容易踩的坑:有人在模型 setPasswordAttr 里写 return md5($value) —— 这等于把密码明文哈希后存库,但无盐值、无迭代、可暴力破解。
- 正确做法:在模型的修改器中调用
Hash::make($value),例如:public function setPasswordAttr($value) { return Hash::make($value); } - 确保 PHP 环境启用了
libsodium(Argon2 支持)或至少有bcrypt;可通过echo defined('PASSWORD_ARGON2ID') ? 'yes' : 'no';验证 - 不要自己实现 salt 生成或 base64 编码——
Hash::make()已包含随机 salt 并按标准格式输出(如$argon2id$v=19$m=65536,t=4,p=3$...)
登录时如何安全比对哈希密码?
千万别用 == 或 === 直接比较哈希字符串,必须用 Hash::check($input, $hashed)。它内部调用 password_verify(),具备恒定时间比较、自动适配不同算法版本等防护能力。
典型错误场景:用户登录时,先查库拿到 password 字段,再用 md5($input) === $dbHash —— 这既绕过了安全比对逻辑,又暴露了哈希格式(比如看到 $2y$ 就知道是 bcrypt,便于针对性爆破)。
- 查询用户后,立即用
Hash::check($request->post('password'), $user->password),返回布尔值 - 若校验失败,统一返回“用户名或密码错误”,不要区分“用户不存在”和“密码错误”,防止用户名枚举
-
Hash::check()兼容旧哈希(如早期用md5存的),但应逐步迁移:新注册/修改密码时强制重哈希,老用户下次登录成功后更新为 Argon2
为什么不能把密码规则写死在前端?
前端 JS 校验(如用 zxcvbn)只是体验优化,不是安全防线。用户禁用 JS、用 curl 绕过、或抓包改请求,都能跳过前端限制。所有强度规则必须在服务端 validate 中二次执行。
一个常被忽略的细节:TP 的验证规则默认不校验空格。如果允许密码开头/结尾带空格(如 " Pass123!"),而前端 trim 了但后端没 trim,就会导致哈希存的是带空格的,登录时却比对无空格版本——直接失败。
- 在验证规则里加
trim:例如'password' => 'require|trim|regex:/.../' - 若业务允许空格作为有效字符(如某些合规要求),则必须前后端一致保留,并在正则中明确允许(如
[\s\S]),同时注意 MySQL 字段要用varchar而非char(后者会右补空格) - 数据库字段长度至少设为 255 —— Argon2 哈希字符串可达 200+ 字符,
varchar(60)只够存 bcrypt
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











