方法
包含的模块
实例公共方法
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(默认值)并且对象响应 generates_token_for(Active Record 会响应)时,会自动配置密码重置令牌(默认有效期为 15 分钟)。
最后,可以通过向 has_secure_password 传递一个哈希来定制重置令牌的到期时间。
has_secure_password reset_token: { expires_in: 1.hour }
要使用 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 123 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_nil: 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) reset_token_expires_in = reset_token.is_a?(Hash) ? reset_token[:expires_in] : DEFAULT_RESET_TOKEN_EXPIRES_IN silence_redefinition_of_method(:"#{attribute}_reset_token_expires_in") define_method(:"#{attribute}_reset_token_expires_in") { reset_token_expires_in } generates_token_for :"#{attribute}_reset", expires_in: reset_token_expires_in 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