跳至内容 跳至搜索
方法
H
包含的模块

实例公共方法

has_secure_password(attribute = :password, validations: true)

添加方法来设置和验证 BCrypt 密码。此机制要求您拥有一个 XXX_digest 属性,其中 XXX 是您所需密码的属性名称。

以下验证将自动添加

  • 创建时密码必须存在

  • 密码长度应小于或等于 72 字节

  • 密码确认(使用 XXX_confirmation 属性)

如果不需要确认验证,只需省略 XXX_confirmation 的值(即,不要为其提供表单字段)。当此属性的值为 nil 时,将不会触发验证。

此外,将创建一个 XXX_challenge 属性。当设置为除 nil 以外的值时,它将针对当前持久化的密码进行验证。此验证依赖于脏跟踪,如 ActiveModel::Dirty 提供的那样;如果未定义脏跟踪方法,此验证将失败。

可以通过将 validations: false 作为参数传递来省略所有上述验证。这允许完全自定义验证行为。

要使用 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
# File activemodel/lib/active_model/secure_password.rb, line 101
def has_secure_password(attribute = :password, validations: 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)

  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
end