tp6中密码入库前自动哈希的唯一可控入口是setpasswordattr,命名须严格为set+字段名首字母大写+attr,仅在save()/create()中触发,必须判空并用password_hash()(推荐password_argon2id或password_default),禁用md5、sha1及crypt。

TP6 的 setPasswordAttr 是密码入库前自动哈希的唯一可控入口,但写错命名、漏判空、用错算法,都会导致加密失效或入库报错。
setPasswordAttr 命名必须严格匹配字段名 + 首字母大写 + Attr
数据库字段叫 password,方法名就必须是 setPasswordAttr,不是 _setPasswordAttr,也不是 set_password_attr。ThinkPHP 6+ 已完全弃用旧版的 _setAttr 写法,模型继承 think\Model 后,只有这种驼峰式命名才能被识别为修改器。
常见错误现象:
- 字段是
user_pwd,却写了setUser_pwdAttr→ 不触发 - 字段是
pass_word,写了setPassWordAttr→ 实际应为setPassWordAttr(下划线后首字母仍需大写) - 模型继承了
think\db\Model而非think\Model→ 修改器直接被忽略
只在 save() / create() 中生效,data() 或数组 update 会绕过
修改器逻辑只在模型调用 save()、create()、allowField()->save() 等完整写入流程中执行。以下操作不会触发 setPasswordAttr:
$user->data(['password' => '123'])->save()User::update(['id' => 1, 'password' => '123'])-
$user->password = '123'; $user->save();(注意:这是赋值后 save,会触发;但若中间调了data()就可能中断)
所以别依赖“赋值即加密”,务必确认走的是模型实例的 save() 流程。
password_hash() 是唯一推荐方案,别用 md5、sha1 或裸调 Crypt
明文密码必须用 password_hash() 哈希,且必须判空:
public function setPasswordAttr($value)
{
return $value ? password_hash($value, PASSWORD_ARGON2ID) : null;
}
关键点:
-
password_hash(null, ...)返回false,会导致字段写入失败,所以必须if ($value)判空 -
PASSWORD_ARGON2ID需 PHP ≥ 7.3 且开启sodium扩展;若环境不支持,退用PASSWORD_DEFAULT(当前等价于 bcrypt) -
think\facade\Crypt是对称加解密,不适用于密码存储——它可逆,而密码必须不可逆 -
md5()和sha1()已被证实不安全,且无盐、无迭代,绝对禁用
读取时不反向解密,验证必须用 password_verify()
setPasswordAttr 只管写入,读取时数据库里存的就是哈希串,模型不会自动做任何处理。验证登录时,必须手动用 password_verify($input, $dbHash) 比对:
- 不能把哈希值再传给
password_hash()去“二次加密”比对 - 不能试图用
Crypt::decrypt()去“还原”密码——这违背密码学设计原则 - 如果业务需要显示“密码已设置”,只能查字段是否非空,而非尝试解密
最易被忽略的一点:修改器里不要访问其他字段(如 $this->email)来动态加盐。TP6 修改器执行时机早于字段批量赋值完成,$this->email 很可能还是空或旧值,结果就是盐不稳定、哈希不可复现。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











