将现有的密码哈希转换为Devise

17
我正在尝试将一个现有的管理员模型转换为Devise。我们已经有了一个密码哈希,但显然不符合Devise的要求。我想要做的是接受登录表单并检查提供的密码与加密密码是否一致。如果不正确,则使用旧的哈希来检查密码,如果匹配,则清空旧的password_hash字段,并将Devise的密码设置为提供的密码并保存模型。
如何最好地前进?我怀疑我需要覆盖某些东西,也许在自定义控制器中,但我不确定如何继续下去。
5个回答

34

您可以让Devise使用新的加密方案对密码进行加密,就像在https://gist.github.com/1704632中所示的那样:

class User < ActiveRecord::Base
  alias :devise_valid_password? :valid_password?

  def valid_password?(password)
    begin
      super(password)
    rescue BCrypt::Errors::InvalidHash
      return false unless Digest::SHA1.hexdigest(password) == encrypted_password
      logger.info "User #{email} is using the old password hashing method, updating attribute."
      self.password = password
      true
    end
  end
end

1
我在测试中遇到了堆栈层级过深的错误,于是我将begin后面的devise_valid_password?(password)替换为了super,似乎效果更好... - Gordon Isnor
不错的解决方案,尽管我还需要进行一些微小的修改 - 我之前使用的是早期版本的Devise,它创建了盐值SHA1哈希。我的检查看起来像这样:digest = "" 10.times { digest = Digest::SHA1.hexdigest("--" << [password_salt, digest, password, nil].flatten.join("--") << "--") } return false unless digest == encrypted_password - Ola Tuvesson
这个能够和标准的使用Bcrypt实现的has_secure_password一起工作吗? - pastullo
如果您正在使用Rails中的has_secure_password,请将Digest::SHA1.hexdigest(password)更改为BCrypt::Password.new(password) - Rafael Oliveira
1
为什么你保留了别名语句?只是以防有一天你想调用旧方法,或者这里很重要吗(我认为不是)? - januszm
显示剩余2条评论

4
使用 Devise 中的 bcrypt 加密器,这是我在处理旧数据时所做的操作:
在 models/user.rb 文件中:
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
alias :devise_valid_password? :valid_password?
def valid_password?(password)
  begin
    devise_valid_password?(password)
  rescue BCrypt::Errors::InvalidHash
    Digest::SHA1.hexdigest(password) == encrypted_password
  end
end

正如您所看到的,当devise遇到无效哈希时,它会抛出InvalidHash异常,这在验证传统用户时会发生。

我使用这个方法来回退到用于创建原始传统哈希的哈希算法。

但是它并不会更改密码,如果需要,可以简单地将其添加到该方法中。


有人在您的网站上尝试很多用户名的一个副作用可能是非常高的CPU使用率,因为他们无意中导致了转换发生。啊,算了,它不会转换。所以旧用户仍然会感到不安全...但这仍然是一个非常聪明且低成本的答案,我喜欢它。 - Kevin

3

首先,您需要将password_salt和encrypted_password复制到新的对象模型中。

我这样做是因为我需要将我的数据库用户导出到另一个应用程序,并且旧应用程序使用devise 1.0.x,而新应用程序使用2.1.x。

Class User < ActiveRecord::Base
 alias :devise_valid_password? :valid_password?
    def valid_password?(password)
      begin
        devise_valid_password?(password)
      rescue BCrypt::Errors::InvalidHash
        salt = password_salt
        digest = nil
        10.times { digest = ::Digest::SHA1.hexdigest('--' << [salt, digest, password, nil].flatten.join('--') << '--') }
        digest
        return false unless digest == encrypted_password
        logger.info "User #{email} is using the old password hashing method, updating attribute."
        self.password = password
        self.password_salt = nil # With this you will knew what object already using the new authentication by devise
        self.save
        true
      end
    end
end

3
如果您正在从SHA512迁移,解决方案比moeffju的SHA1解决方案更复杂:
def valid_password?(password)
  if has_legacy_password?
    return false unless valid_legacy_password?(password)
    convert_legacy_password!(password)
    true
  else
    super(password)
  end
end

protected

def has_legacy_password?
  password_salt.present?
end

def convert_legacy_password!(password)
  self.password = password
  self.password_salt = nil
  self.save
end

def valid_legacy_password?(password)
  stretches = 10
  salt = password_salt
  pepper = nil
  digest = pepper

  stretches.times do
    tokens = [salt, digest, password, pepper]
    digest = Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
  end

  Devise.secure_compare(encrypted_password, digest)
end

请务必用你用来加密密码的值替换stretchespepper


-2

按照Thomas Dippel的指示,我已经创建了一个Gist来更新密码: https://gist.github.com/1578362

    # Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
#SOURCES OF SOLUTION:
# https://dev59.com/xm025IYBdhLWcg3wRDq8
# https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/crypto_providers/sha512.rb
# https://github.com/plataformatec/devise/blob/master/lib/devise/encryptors/authlogic_sha512.rb

alias :devise_valid_password? :valid_password?
def valid_password?(password)
  debugger
  begin
    devise_valid_password?(password)
  rescue BCrypt::Errors::InvalidHash
    stretches = 20
    digest = [password, self.password_salt].flatten.join('')
    stretches.times {digest = Digest::SHA512.hexdigest(digest)}
    if digest == self.encrypted_password
      #Here update old Authlogic SHA512 Password with new Devise ByCrypt password
      # SOURCE: https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
      # Digests the password using bcrypt.
      # Default strategy for Devise is BCrypt
      # def password_digest(password)
      # ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
      # end
      self.encrypted_password = self.password_digest(password)
      self.save
      return true
    else
      # If not BCryt password and not old Authlogic SHA512 password Dosn't my user
      return false
    end
  end
end 

你可以直接使用 "self.password = password",让 Devise 在内部处理所有加密工作。 - moeffju
我认为这是不可能的,因为我想使用BCrypt作为加密器,但我的密码是使用authlogic sha512加密的。 - Andrés Gutiérrez
可以的,我已经做过了。例如:https://gist.github.com/1704632 这就是你需要的全部内容。校验SHA并更新为Bcrypt。 - moeffju
感谢moeffju的回应。我做错了,我多做了一些工作。encrypted_password是在哪里定义的? - Andrés Gutiérrez
Devise 提供了在用户模型上使用的 encrypted_password 方法。 - moeffju

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接