如何复现Sequel框架中Bcrypt加密密码的validates_min_length验证错误
解决Sequel中Bcrypt密码的最小长度验证问题
我完全懂你遇到的麻烦——Sequel的validates_min_length默认直接校验数据库字段的值,但Bcrypt加密后的密码哈希长度远超你要限制的明文长度,直接用它验证:password字段根本达不到预期效果。而且通过password=赋值时,明文会被立刻加密,也没法抓明文来做验证。下面给你两个实用的解决方案:
方案1:用虚拟属性分离明文与哈希(推荐)
这个方法逻辑最清晰,通过一个虚拟属性接收明文密码,专门对它做长度验证,再在保存前加密存入数据库字段。
require 'sequel' require 'bcrypt' DB = Sequel.sqlite DB.create_table(:users) do primary_key :id String :name, null: false, unique: true String :password, null: false end class User < Sequel::Model plugin :validation_helpers include BCrypt # 定义虚拟属性,专门接收前端传入的明文密码 attr_accessor :password_plain def validate super # 只对明文密码做最小长度验证 validates_min_length 6, :password_plain, message: "密码长度不能少于6个字符" end # 保存前把明文加密后赋值给数据库的password字段 before_save do if password_plain.present? self.password = Password.create(password_plain) end end end # 测试验证效果 user = User.new(name: "craig", password_plain: "short") user.valid? # 返回 false puts user.errors.full_messages # 输出: ["密码长度不能少于6个字符"] user.password_plain = "longenough123" user.valid? # 返回 true user.save # 成功保存,password字段存储的是加密后的哈希
方案2:重写password=方法缓存明文
如果你不想加额外的虚拟属性,可以重写赋值方法,把明文临时缓存下来,在验证时用缓存的值做校验。
class User < Sequel::Model plugin :validation_helpers include BCrypt # 临时存储明文密码,用于验证 attr_reader :raw_password def password=(value) # 只有当传入的是明文(不是已加密的BCrypt::Password实例)时才缓存 @raw_password = value unless value.is_a?(Password) # 调用父类方法完成加密赋值 super(Password.create(value)) end def validate super # 仅当有新的明文密码时才做长度验证 if @raw_password.present? validates_min_length 6, :raw_password, message: "密码长度不能少于6个字符" end end end # 测试 user = User.new(name: "craig", password: "12345") user.valid? # false puts user.errors.full_messages # 输出: ["raw_password 密码长度不能少于6个字符"] user.password = "123456" user.valid? # true user.save # 成功保存
注意事项
- 如果是更新用户密码的场景,要注意:如果用户没有输入新密码(即
password_plain或raw_password为空),不要触发长度验证,上面的代码已经通过present?判断处理了这个情况。 - 方案1的虚拟属性方式更易维护,因为它明确区分了“用户输入的明文”和“数据库存储的哈希”两个概念,后续扩展其他验证(比如密码复杂度)也更方便。
内容的提问来源于stack exchange,提问作者craig
相关产品推荐
相关产品推荐

