我开始研究一个已经有很多代码的项目。它是一个Ruby on Rail应用程序,它使用Devise进行用户身份验证。该应用程序的一个要求是,当用户更改其密码时,不允许他们使用与之前使用的最后三个密码相同的密码。为了实现这一点,有一个表包含给定用户的密码历史记录。这些密码是在用户更改密码之前存在的加密密码的副本。
以下是问题所在。我们有一个密码更改表单,用于收集给定用户的新密码。我需要能够获取新密码并对其进行加密,以便我可以将新密码的加密值与历史记录中旧密码的加密值进行匹配。
技术资料
Rails版本3.0.9
设计版本1.3.4
使用标准BCrypt和Devise。 bcrypt_ruby版本2.1.4
为此,我们将覆盖Devise支持的reset_password方法。这允许我们在用户控制器中引入我们自己的方法has_repeated_password。
我开始使用的has_repeated_password版本如下:
def has_repeated_password?
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
history.encrypted_password == self.class.encryptor_class.digest(self.password, self.class.stretches, history.password_salt, self.class.pepper)
end
end
这里的问题是从不定义加密器类,每次运行此例程时都会导致错误。即使有很多例子声称这可行,但当Devise使用默认加密时,我无法让它工作。
第二次尝试是以下代码:
def has_repeated_password?<br>
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
pwd = self.password_digest(self.password)
history.encrypted_password == pwd
end
end
在这种情况下,我从未获得与任何存储密码匹配的密码,即使我已经验证数据库中的密码是我期望的。
我一直在尝试挖掘Devise代码,看看我能在那里找到什么。我知道,当它将用户收集的密码与存储的密码进行匹配时,身份验证必须以某种方式执行此操作。
任何帮助都将不胜感激。
答案 0 :(得分:2)
我想我找到了解决自己问题的方法。关键的一点是,我试图获得一个加密密码,该密码不属于与Devise相关的用户模型(不再是)。这个解决方案确实假设Devise将使用Bcrypt作为标准加密工具(不记得Devise的哪个版本移动)。 Bcrypt / Devise实际上在加密密码中掩盖了密码的盐。如果你有盐和胡椒,你可以获得相同的密码来生成相同的加密值。
所以这里是上面引用的例程的更新代码:
def has_repeated_password?
return false if self.new_record? || self.version == 1
histories = self.versions.find(:all, :order => 'version DESC', :limit => 3)
histories.detect do |history|
bcrypt = ::BCrypt::Password.new(history.encrypted_password)
password = ::BCrypt::Engine.hash_secret("#{self.password}#{self.class.pepper}", bcrypt.salt)
password == history.encrypted_password
end
end
这里的关键是Bcyrpt对象必须使用生成原始密码的相同盐来创建现有的加密密码。这是通过为其提供我存储的历史加密密码(history.encrypted_password)来实现的。其中一个关键因素是历史密码和建议的新密码使用相同的胡椒,由Devise管理。因此,通过使用带有预期新密码的Engne.has_secret调用,可以将其与历史密码进行比较。
我不得不将bcrypt代码移到这里,因为Devise支持的所有密码方法都假定您要对当前用户对象的用户密码进行操作。