方法
包含的模块
实例公共方法
has_secure_password(attribute = :password, validations: true, reset_token: true) 链接
添加方法来设置和验证 BCrypt 密码。此机制要求您具有 XXX_digest
属性,其中 XXX
是您所需密码的属性名称。
以下验证将自动添加
-
创建时密码必须存在
-
密码长度应小于或等于 72 字节
-
密码确认(使用
XXX_confirmation
属性)
如果不需要确认验证,只需省略 XXX_confirmation
的值(即,不要为此提供表单字段)。当此属性的值为 nil
时,将不会触发验证。
此外,还创建了一个 XXX_challenge
属性。当设置为除 nil
以外的值时,它将根据当前持久化的密码进行验证。此验证依赖于 ActiveModel::Dirty
提供的脏跟踪;如果未定义脏跟踪方法,此验证将失败。
所有上述验证都可以通过传递 validations: false
作为参数来省略。这允许完全自定义验证行为。
最后,当 reset_token
设置为 true(默认情况下为 true)并且对象响应 generates_token_for
(Active Records 响应)时,会自动配置一个在发布后 15 分钟内有效的密码重置令牌。
要使用 has_secure_password
,请将 bcrypt (~> 3.1.7) 添加到您的 Gemfile 中
gem "bcrypt", "~> 3.1.7"
示例
使用 Active Record(它自动包含 ActiveModel::SecurePassword
)
# Schema: User(name:string, password_digest:string, recovery_password_digest:string)
class User < ActiveRecord::Base
has_secure_password
has_secure_password :recovery_password, validations: false
end
user = User.new(name: "david", password: "", password_confirmation: "nomatch")
user.save # => false, password required
user.password = "vr00m"
user.save # => false, confirmation doesn't match
user.password_confirmation = "vr00m"
user.save # => true
user.authenticate("notright") # => false
user.authenticate("vr00m") # => user
User.find_by(name: "david")&.authenticate("notright") # => false
User.find_by(name: "david")&.authenticate("vr00m") # => user
user.recovery_password = "42password"
user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
user.save # => true
user.authenticate_recovery_password("42password") # => user
user.update(password: "pwn3d", password_challenge: "") # => false, challenge doesn't authenticate
user.update(password: "nohack4u", password_challenge: "vr00m") # => true
user.authenticate("vr00m") # => false, old password
user.authenticate("nohack4u") # => user
有条件地要求密码
class Account
include ActiveModel::SecurePassword
attr_accessor :is_guest, :password_digest
has_secure_password
def errors
super.tap { |errors| errors.delete(:password, :blank) if is_guest }
end
end
account = Account.new
account.valid? # => false, password required
account.is_guest = true
account.valid? # => true
使用密码重置令牌
user = User.create!(name: "david", password: "123", password_confirmation: "123")
token = user.password_reset_token
User.find_by_password_reset_token(token) # returns user
# 16 minutes later...
User.find_by_password_reset_token(token) # returns nil
# raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
User.find_by_password_reset_token!(token)
来源:显示 | 在 GitHub 上
# File activemodel/lib/active_model/secure_password.rb, line 116 def has_secure_password(attribute = :password, validations: true, reset_token: true) # Load bcrypt gem only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. begin require "bcrypt" rescue LoadError warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install." raise end include InstanceMethodsOnActivation.new(attribute, reset_token: reset_token) if validations include ActiveModel::Validations # This ensures the model has a password by checking whether the password_digest # is present, so that this works with both new and existing records. However, # when there is an error, the message is added to the password attribute instead # so that the error message will make sense to the end-user. validate do |record| record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present? end validate do |record| if challenge = record.public_send(:"#{attribute}_challenge") digest_was = record.public_send(:"#{attribute}_digest_was") if record.respond_to?(:"#{attribute}_digest_was") unless digest_was.present? && BCrypt::Password.new(digest_was).is_password?(challenge) record.errors.add(:"#{attribute}_challenge") end end end # Validates that the password does not exceed the maximum allowed bytes for BCrypt (72 bytes). validate do |record| password_value = record.public_send(attribute) if password_value.present? && password_value.bytesize > ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED record.errors.add(attribute, :password_too_long) end end validates_confirmation_of attribute, allow_blank: true end # Only generate tokens for records that are capable of doing so (Active Records, not vanilla Active Models) if reset_token && respond_to?(:generates_token_for) generates_token_for :"#{attribute}_reset", expires_in: 15.minutes do public_send(:"#{attribute}_salt")&.last(10) end class_eval <<-RUBY, __FILE__, __LINE__ + 1 silence_redefinition_of_method :find_by_#{attribute}_reset_token def self.find_by_#{attribute}_reset_token(token) find_by_token_for(:#{attribute}_reset, token) end silence_redefinition_of_method :find_by_#{attribute}_reset_token! def self.find_by_#{attribute}_reset_token!(token) find_by_token_for!(:#{attribute}_reset, token) end RUBY end end