Rails 4 - 仅当当前密码正确时才允许更改密码
Rails 4 - Allow password change only if current password is correct
在我的应用中,用户可以编辑他们的个人资料信息。在编辑个人资料表单上,用户可以更改所有字段(姓名、职位等)。在同一个表单上有三个字段:current_password
、password
和 password_confirmation
。我正在使用 bcrypt
的 has_secure_password
功能进行密码验证。我根本没有使用 Devise。
我希望用户只有提供了正确的当前密码才能更改密码。我之前在我的用户控制器的 update
方法中使用以下代码进行了此操作:
# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password
if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password])
# Add an error that states the user's current password is incorrect
@user.errors.add(:base, "Current password is incorrect.")
else
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
end
但是,这种方法的问题在于,如果当前密码不正确,它会放弃对用户模型的所有更改。如果当前密码不正确,我想保存对用户模型的所有更改,但不保存密码更改。我试过像这样拆分 IF 语句:
# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password
if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password])
# Add an error that states the user's current password is incorrect
@user.errors.add(:base, "Current password is incorrect.")
end
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
这不起作用,因为即使当前密码不正确,用户也可以更改 his/her 密码。单步执行代码时,虽然在@user
中添加了"Current password is incorrect."错误,但在运行之后通过update_attributes
方法,似乎忽略了这个错误信息。
顺便说一下,current_password
字段是我的用户模型中的一个虚拟属性:
attr_accessor :current_password
几个小时以来,我一直在努力解决这个问题,所以我真的需要一些帮助。
谢谢!
解决方案
多亏了 papirtiger,我才开始工作。我根据他的回答稍微更改了代码。下面是我的代码。请注意,任一代码片段都可以正常工作。
在用户模型中 (user.rb)
class User < ActiveRecord::Base
has_secure_password
attr_accessor :current_password
# Validate current password when the user is updated
validate :current_password_is_correct, on: :update
# Check if the inputted current password is correct when the user tries to update his/her password
def current_password_is_correct
# Check if the user tried changing his/her password
if !password.blank?
# Get a reference to the user since the "authenticate" method always returns false when calling on itself (for some reason)
user = User.find_by_id(id)
# Check if the user CANNOT be authenticated with the entered current password
if (user.authenticate(current_password) == false)
# Add an error stating that the current password is incorrect
errors.add(:current_password, "is incorrect.")
end
end
end
end
我的用户控制器中的代码现在很简单:
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
所以从用户的角度考虑,如果有人输入了错误的密码,您不希望其他内容也不要更改吗?通常人们会更新密码,其中只有电子邮件和密码。如果当前密码不正确,则不要更新任何内容。
如果你必须这样做,那么只需移动逻辑并有两组参数或从参数中删除密码。这是它的伪代码。
if not_authenticated_correctly
params = params_minus_password_stuff (or use slice, delete, etc)
end
#Normal update user logic
您可以在模型级别添加自定义验证,以检查密码是否已更改:
class User < ActiveRecord::Base
has_secure_password
validate :current_password_is_correct,
if: :validate_password?, on: :update
def current_password_is_correct
# For some stupid reason authenticate always returns false when called on self
if User.find(id).authenticate(current_password) == false
errors.add(:current_password, "is incorrect.")
end
end
def validate_password?
!password.blank?
end
attr_accessor :current_password
end
另一种方法是使用自定义验证器而不是将此验证嵌入到模型中。您可以将这些自定义验证器存储在 app/validators 中,它们将由 Rails 自动加载。我把这个叫做 password_match_validator.rb。
除了可重用之外,此策略还消除了在身份验证时重新查询 User 的需要,因为 User 实例由 rails 作为 "record" 参数自动传递给验证器。
class PasswordMatchValidator < ActiveModel::EachValidator
# Password Match Validator
#
# We need to validate the users current password
# matches what we have on-file before we change it
#
def validate_each(record, attribute, value)
unless value.present? && password_matches?(record, value)
record.errors.add attribute, "does not match"
end
end
private
# Password Matches?
#
# Need to validate if the current password matches
# based on what the password_digest was. has_secure_password
# changes the password_digest whenever password is changed.
#
# @return Boolean
#
def password_matches?(record, value)
BCrypt::Password.new(record.password_digest_was).is_password?(value)
end
end
将验证器添加到项目后,您可以在任何模型中使用它,如下所示。
class User < ApplicationRecord
has_secure_password
# Add an accessor so you can have a field to validate
# that is seperate from password, password_confirmation or
# password_digest...
attr_accessor :current_password
# Validation should only happen if the user is updating
# their password after the account has been created.
validates :current_password, presence: true, password_match: true, on: :update, if: :password_digest_changed?
end
如果您不想将 attr_accessor 添加到每个模型中,您可以将其与一个问题结合起来,但这可能有点矫枉过正。如果您有针对管理员和用户的单独模型,则效果很好。请注意,文件名、class 名称和验证器上使用的密钥都必须匹配。
只是张贴它,适用于 ror 6.x
form.erb 文件:
<div class="field">
<%= form.label :current_password, 'Current password:' %>
<%= form.password_field :current_password, size: 40 %>
</div>
<div class="field">
<%= form.label :password, 'Password:'%>
<%= form.password_field :password, size:40 %>
</div>
<div class="field">
<%= form.label :password_confirmation, 'Confirm:' %>
<%= form.password_field :password_confirmation, id: :user_password_confirmation, size:40 %>
</div>
<div class="actions">
<%= form.submit %>
</div>
user.rb:
has_secure_password
# virtual attribute
attr_accessor :current_password
# Validate current password when the user is updated
validate :current_password_is_correct, on: :update
# Check if the inputted current password is correct when the user tries to update his/her password
def current_password_is_correct
# Check if the user tried changing his/her password
return if password.blank?
# Get a reference to the user since the "authenticate" method always returns false when calling on itself (for some reason)
user = User.find(id)
# Check if the user CANNOT be authenticated with the entered current password
if user.authenticate(current_password) == false
# Add an error stating that the current password is incorrect
errors.add(:current_password, "is incorrect.")
end
end
users_controller.rb:
只需要在 def user_params 中添加“:current_password” 否则传递更改将不起作用,并且在服务器日志中将写入:
Unpermitted parameter: :current_password
在我的应用中,用户可以编辑他们的个人资料信息。在编辑个人资料表单上,用户可以更改所有字段(姓名、职位等)。在同一个表单上有三个字段:current_password
、password
和 password_confirmation
。我正在使用 bcrypt
的 has_secure_password
功能进行密码验证。我根本没有使用 Devise。
我希望用户只有提供了正确的当前密码才能更改密码。我之前在我的用户控制器的 update
方法中使用以下代码进行了此操作:
# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password
if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password])
# Add an error that states the user's current password is incorrect
@user.errors.add(:base, "Current password is incorrect.")
else
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
end
但是,这种方法的问题在于,如果当前密码不正确,它会放弃对用户模型的所有更改。如果当前密码不正确,我想保存对用户模型的所有更改,但不保存密码更改。我试过像这样拆分 IF 语句:
# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password
if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password])
# Add an error that states the user's current password is incorrect
@user.errors.add(:base, "Current password is incorrect.")
end
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
这不起作用,因为即使当前密码不正确,用户也可以更改 his/her 密码。单步执行代码时,虽然在@user
中添加了"Current password is incorrect."错误,但在运行之后通过update_attributes
方法,似乎忽略了这个错误信息。
顺便说一下,current_password
字段是我的用户模型中的一个虚拟属性:
attr_accessor :current_password
几个小时以来,我一直在努力解决这个问题,所以我真的需要一些帮助。
谢谢!
解决方案
多亏了 papirtiger,我才开始工作。我根据他的回答稍微更改了代码。下面是我的代码。请注意,任一代码片段都可以正常工作。
在用户模型中 (user.rb)
class User < ActiveRecord::Base
has_secure_password
attr_accessor :current_password
# Validate current password when the user is updated
validate :current_password_is_correct, on: :update
# Check if the inputted current password is correct when the user tries to update his/her password
def current_password_is_correct
# Check if the user tried changing his/her password
if !password.blank?
# Get a reference to the user since the "authenticate" method always returns false when calling on itself (for some reason)
user = User.find_by_id(id)
# Check if the user CANNOT be authenticated with the entered current password
if (user.authenticate(current_password) == false)
# Add an error stating that the current password is incorrect
errors.add(:current_password, "is incorrect.")
end
end
end
end
我的用户控制器中的代码现在很简单:
# Try to update the user
if @user.update_attributes(the_params)
# Notify the user that his/her profile was updated
flash.now[:success] = "Your changes have been saved"
end
所以从用户的角度考虑,如果有人输入了错误的密码,您不希望其他内容也不要更改吗?通常人们会更新密码,其中只有电子邮件和密码。如果当前密码不正确,则不要更新任何内容。
如果你必须这样做,那么只需移动逻辑并有两组参数或从参数中删除密码。这是它的伪代码。
if not_authenticated_correctly
params = params_minus_password_stuff (or use slice, delete, etc)
end
#Normal update user logic
您可以在模型级别添加自定义验证,以检查密码是否已更改:
class User < ActiveRecord::Base
has_secure_password
validate :current_password_is_correct,
if: :validate_password?, on: :update
def current_password_is_correct
# For some stupid reason authenticate always returns false when called on self
if User.find(id).authenticate(current_password) == false
errors.add(:current_password, "is incorrect.")
end
end
def validate_password?
!password.blank?
end
attr_accessor :current_password
end
另一种方法是使用自定义验证器而不是将此验证嵌入到模型中。您可以将这些自定义验证器存储在 app/validators 中,它们将由 Rails 自动加载。我把这个叫做 password_match_validator.rb。
除了可重用之外,此策略还消除了在身份验证时重新查询 User 的需要,因为 User 实例由 rails 作为 "record" 参数自动传递给验证器。
class PasswordMatchValidator < ActiveModel::EachValidator
# Password Match Validator
#
# We need to validate the users current password
# matches what we have on-file before we change it
#
def validate_each(record, attribute, value)
unless value.present? && password_matches?(record, value)
record.errors.add attribute, "does not match"
end
end
private
# Password Matches?
#
# Need to validate if the current password matches
# based on what the password_digest was. has_secure_password
# changes the password_digest whenever password is changed.
#
# @return Boolean
#
def password_matches?(record, value)
BCrypt::Password.new(record.password_digest_was).is_password?(value)
end
end
将验证器添加到项目后,您可以在任何模型中使用它,如下所示。
class User < ApplicationRecord
has_secure_password
# Add an accessor so you can have a field to validate
# that is seperate from password, password_confirmation or
# password_digest...
attr_accessor :current_password
# Validation should only happen if the user is updating
# their password after the account has been created.
validates :current_password, presence: true, password_match: true, on: :update, if: :password_digest_changed?
end
如果您不想将 attr_accessor 添加到每个模型中,您可以将其与一个问题结合起来,但这可能有点矫枉过正。如果您有针对管理员和用户的单独模型,则效果很好。请注意,文件名、class 名称和验证器上使用的密钥都必须匹配。
只是张贴它,适用于 ror 6.x
form.erb 文件:
<div class="field">
<%= form.label :current_password, 'Current password:' %>
<%= form.password_field :current_password, size: 40 %>
</div>
<div class="field">
<%= form.label :password, 'Password:'%>
<%= form.password_field :password, size:40 %>
</div>
<div class="field">
<%= form.label :password_confirmation, 'Confirm:' %>
<%= form.password_field :password_confirmation, id: :user_password_confirmation, size:40 %>
</div>
<div class="actions">
<%= form.submit %>
</div>
user.rb:
has_secure_password
# virtual attribute
attr_accessor :current_password
# Validate current password when the user is updated
validate :current_password_is_correct, on: :update
# Check if the inputted current password is correct when the user tries to update his/her password
def current_password_is_correct
# Check if the user tried changing his/her password
return if password.blank?
# Get a reference to the user since the "authenticate" method always returns false when calling on itself (for some reason)
user = User.find(id)
# Check if the user CANNOT be authenticated with the entered current password
if user.authenticate(current_password) == false
# Add an error stating that the current password is incorrect
errors.add(:current_password, "is incorrect.")
end
end
users_controller.rb:
只需要在 def user_params 中添加“:current_password” 否则传递更改将不起作用,并且在服务器日志中将写入:
Unpermitted parameter: :current_password