Rails控制台可生成password_digest,控制器逻辑为何失效?
手动实现Rails身份认证时,表单创建用户的password_digest始终为nil
我手动实现Rails身份认证(不使用Devise),集成ActiveModel::SecurePassword时遇到异常:
- Rails控制台创建用户时,
password_digest能正常生成,验证也生效 - 通过注册表单调用控制器逻辑创建用户时,
password_digest始终为nil
已尝试的操作:
- 在User模型中引入
ActiveModel::SecurePassword - 启用bcrypt gem并执行
bundle install - 控制台操作正常(示例代码):
3.2.1 :137 > new_user = User.new(first_name: "test", last_name: "tester", email: "foo@bar.com", password: "123123123") => #<User:0x0000000107c5bf40 id: nil, first_name: "test", last_name: "tester", email: "foo@bar.com", password: nil, created_at: nil, updated_at: nil> 3.2.1 :138 > new_user.save TRANSACTION (0.7ms) BEGIN User Exists? (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "foo@bar.com"], ["LIMIT", 1]] User Create (0.5ms) INSERT INTO "users" ("first_name", "last_name", "email", "password", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["first_name", "test"], ["last_name", "tester"], ["email", "foo@bar.com"], ["password", "[FILTERED]"], ["created_at", "2023-03-07 03:36:07.501992"], ["updated_at", "2023-03-07 03:36:07.501992"]] TRANSACTION (1.3ms) COMMIT => true 3.2.1 :139 > new_user.password_digest => "$2a$12$aIcAuAdSnJhrLw0bWHTh1OgTTmUioB2ra3h12cFNs/IwnJdyRYJEW" # 注:密码已通过Bcrypt成功加密
但表单创建的用户password_digest为nil:
3.2.1 :140 > latest_user = User.last User Load (1.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<User:0x0000000107d75ca0 id: 27, first_name: "Allday", last_name: "Everyday", email: "allday@everyday.com", password: nil, created_at: Tue, 07 Mar 2023 03:39:13.986217000 UTC +00:00, updated_at: Tue, 07 Mar 2023 03:39:13.986217000 UTC +00:00> 3.2.1 :141 > latest_user.password_digest => nil # 注:digest为nil!
相关代码
user.rb
# == Schema Information # # Table name: users # # id :bigint not null, primary key # email :string # first_name :string # last_name :string # password :string # password_digest :string # created_at :datetime not null # updated_at :datetime not null # class User < ApplicationRecord include ActiveModel::SecurePassword has_secure_password has_secure_password :recovery_password, validations: false before_save :downcase_email validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, uniqueness: true, presence: true validates :first_name, length: {minimum: 2, maximum: 100}, presence: true validates :last_name, length: {minimum: 2, maximum: 100}, presence: true validates :password, length: {minimum: 8, maximum: 25}, presence: true attr_accessor :password_digest, :recovery_password_digest private def downcase_email self.email = email.downcase end end
user_controller.rb
class UserController < ApplicationController # post '/user/register' def create user = User.new(user_params) begin user.save! session[:user_id] = user.id return render json: {success: true, status: 200} rescue => exception return render json: {success: false, status: 422, error: exception.message} end end private def user_params params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation) end end
问题原因及解决方案
核心问题
你在User模型中添加的attr_accessor :password_digest, :recovery_password_digest完全覆盖了Rails为数据库字段自动生成的getter/setter方法。
has_secure_password的工作逻辑是监听password属性的赋值,自动生成加密后的password_digest并写入数据库列。但手动定义的attr_accessor把password_digest变成了内存中的虚拟属性,不再关联数据库字段,导致生成的digest无法持久化。
控制台操作看似正常,是因为save后直接调用实例的password_digest取的是内存中的临时值,如果你重新查询该用户,会发现数据库里的password_digest同样是nil。
修复步骤
直接删除User模型中的这一行:
attr_accessor :password_digest, :recovery_password_digest
额外优化建议
- 数据库表中不需要
password字段,has_secure_password只需要password_digest列存储加密后的密码,password只是用于接收明文输入的虚拟属性,建议移除数据库中的password字段。 include ActiveModel::SecurePassword可以省略,因为ApplicationRecord继承自ActiveRecord::Base,has_secure_password会自动引入所需模块。
内容的提问来源于stack exchange,提问作者twk
相关产品推荐
相关产品推荐

