但愿你没有在数据库里直接存储明文密码!为了防治密码被窃取,数据库中存储的始终应该是某种形式的哈希值,而非明文密码。但是,Rails 3.1 之后可以利用 has_secure_password 来实现。这个 Rails 内部的机制充分考虑了密码验证和加密。它需要 model 中有一个 password_digest 字段,用来存储加密后的密码。
1 | $ rails g model user email:string password_digest:string |
has_secure_password 这会为 model 加上 password 和 password_confirmation 两个属性。这两个属性是 model 的一部分,但是并不存在于数据库中,因为我们并不需要存储明文密码。
1 | test 'fails because no password' do |
- 不提供密码,用户创建失败
- 密码过短,用户创建失败
- 密码合法,用户创建成功
用 rails test 跑这些用例时,第二个用例会失败。因为默认 Rails 不会加入密码长度的验证。现在我们在 model 中自己加上。
1 | class User < ApplicationRecord |
再跑一次测试,这次用例都通过了。检查一下数据库会发现 users 表的 password_digest 列存储着经过加密的数值,这正是我们想要的!现在来做验证的部分。假设一个新的注册用户现在想要登录。你可以这样进行验证:
1 | User.find_by(email: "EMAIL").authenticate("CLEAR_TEXT_PASSWORD") |
如果从 HTTP 请求中传来的用户名和明文密码都正确,将从数据库中返回一个有效用户。否则返回 nil has_secure_password 只在对象创建时对密码进行验证。之后的操作便不再验证(例如 update )。这个可以接受,因为这意味着你可以从数据库加载用户,在不知道密码的情况下修改和保存用户。has_secure_password 还为 model 提供了 password_confirmation 属性。只有当该属性为非 nil 值时才触发验证。如果该属性非 nil,则其值必须与 password 属性相等。让我们针对该属性再加两个测试用例。
1 | test "fails because password confirmation doesnt match" do |
为了让测试通过,在 model 中再加入一行代码。
1 | class User < ApplicationRecord |
validates_confirmation_of :password 将会检查密码的一致性。Rails 不强制使用 password_confirmation,但是你需要的话可以像这么来用。我确实很喜欢这个功能,因为它节省了很多代码和开发时间。而且对大部分应用来说够用了。has_secure_password 会自动加入以下验证规则:
- 在创建时密码必须存在
- 密码长度小于等于72字符
- 确认密码,使用 password_confirmation 属性
在实际项目中,通过第三方应用登录的用户是不需要密码的,这时可以通过传递 validations: false 参数移除验证规则。
1 | has_secure_password validations: false |
模块验证规则查看
1 | User.validators |
模块中某属性验证规则查看
1 | User.validators_on(:password) |