2012-11-19 65 views
0

我对Rails有点新,并且正在使用ActiveRecord设计用户模型。在这个模型中,我有一个密码属性,用于保存用户密码的散列。在ActiveRecord模型中隐藏属性

我想直接删除该属性的读取和设置。但是,我似乎无法找到使用Rails控制台时删除访问器的方法。到目前为止,唯一可行的解​​决方案是显式重写访问器方法的密码,我不想重写它们,我希望访问器已经消失 - 或者至少是读者。

这里是我的模型:

class User < ActiveRecord::Base 

    // various associations 

    def password_correct?(password) 
    read_attribute(:password) == hash(password) 
    end 

    def password=(password) 
    write_attribute(:password, hash(password)) 
    end 

    def password 
    "get your dirty fingers off this attribute" 
    end 

    private 

    def hash(input) 
    Digest::SHA2.new(512).update(input).hexdigest 
    end 

end 

任何想法如何做到这一点,或者这种方式有什么缺点?

+0

见接受的答案这个[问题](http://stackoverflow.com/questions/3764899/is-there-a-way-to-make-rails-activerecord-attributes-private)。 – cdesrosiers

+3

这是什么意思?它不提供任何安全性 - 控制台用户总是可以通过SELECT语句从数据库获取属性。 – zetetic

+0

你不应该使用'has_secure_password'选项吗? – toxaq

回答

1

一个Padrino应用基于上述答案正在执行,我已经做了一些实验,以获得期望的结果。我最终创建了一个“private”password_hash属性和一个名为password的虚拟访问器。

我在这个过程中做了一些意见:

  • 看来,ActiveRecord的没有私人属性的任何概念。使用诸如private :password, :password=之类的符号使私有访问器方法不是一种选择,因为Rails在实例化模型时抛出了NameError: undefined method,因为模型本身没有定义这两种方法(它们似乎从ActiveRecord::Base继承)。

  • 用纯粹的东西覆盖password_hash访问器非常适合防止对属性的操作,但它也意味着ActiveRecord本身在更新password_hash属性时失败,因为它调用的是空实现。

因此,使访问器专用失败,因为它们在实际模型中未定义。定义它们也失败了,因为它打破了ActiveRecord。所以,你可以做什么?

我做了两个和更多。我使访问器是私有的,通过调用super来定义它们并实现。这可以防止控制器(和导轨控制台)通过抛出NoMethodError来访问它们,但不会拒绝ActiveRecord。

一个侧面说明:验证问题

一个问题,我遇到了我的做法被打破验证。在password_hash上执行最小长度是不好的,因为任何密码(甚至没有)会导致128个字符的SHA512哈希。所以验证散列没什么意义。相反,我向虚拟密码访问器添加了验证,并添加了一个回调函数,用于检查是否已设置虚拟访问器属性,如果是,则将其哈希并写入password_hash属性。

最终实现

我实现弄成这样:

class User < ActiveRecord::Base 
    attr_accessible :first_name, :last_name, :email 
    attr_accessor :password 
    validates :password, :length => { :minimum => 8 }, :if => :password_changed? 
    validates :first_name, :last_name, :email, presence: true 
    # Various associations 
    before_save :hash_password 

    def password_correct?(p) 
    if(password.present?) 
     password == p 
    else 
     read_attribute(:password_hash) == hash_string(p) 
    end 
    end 

    def role_symbols 
    roles.collect do |r| 
     r.name.to_sym 
    end 
    end 

    private 

    def hash_string(input) 
    Digest::SHA2.new(512).update(input).hexdigest 
    end 

    def hash_password 
    if(password.present?) 
     write_attribute(:password_hash, hash_string(password)) 
     self.password = nil 
    end 
    end 

    def password_changed? 
    password.present? or new_record? 
    end 

    def password_hash 
    super 
    end 

    def password_hash=(p) 
    super 
    end 

end 
0

您可以轻松存取私人与私人的方法,下面添加模型中的行:

private :password, :password= 

此外,我建议你不要覆盖密码字段访问。也许你可以在数据库中命名字段password_hash,并使该列的访问者保密。然后按预期编写您的方法password,password=

如果它不符合您的需求,请您解释一下为什么?为什么你想要更好的解决方案?

0

我在寻找应用程序的身份验证解决方案时偶然发现了devise之类的宝石,最后以http://bcrypt-ruby.rubyforge.org/为例介绍了这种更简单的方法。

require 'bcrypt' 

class Account < ActiveRecord::Base 
    attr_accessor :password, :password_confirmation, :role 

    def password 
    @password ||= BCrypt::Password.new(crypted_password) 
    end 

    def password=(new_password) 
    @password = BCrypt::Password.create(new_password) 
    self.crypted_password = @password 
    end 
end 

编辑:演示,如结合ActivecRecord和PostgreSQL

[6] pry(main)> a = Account.new 
=> #<Account id: nil, name: nil, email: nil, role: nil, uid: nil, provider: nil, created_at: nil, updated_at: nil, crypted_password: nil> 
[7] pry(main)> a.password = "asdf" 
=> "asdf" 
[8] pry(main)> a.password 
=> "$2a$10$9udQKttf5zFqCv7da9ZY0uMsWYlbeGK3apEkIY6x05KND1v3vOkh2" 
[9] pry(main)> a.password == "asdf" 
=> true 
0

让我告诉为什么我不得不去通过类似的路径,你一个小故事。我曾经不得不“退订”我的模型的很多属性/列。

的事情是,我想通过read_attribute方法来达到的列值。我在寻求在几周后将这些列从数据库中删除,因此我希望首先对这些“不推荐”列中的数据进行优雅迁移(并将它们移植到新表中),但我仍然希望使用整个应用程序使用相同的旧属性访问器方法,但他们会使用新的数据库结构。因此,我简单地写了method_missing来捕捉所有的旧列(通过正则表达式),并将他们的动作重定向到一个新的逻辑中,而不是简单的覆盖方法(读者和写者)。但是,如果我能够“定义”旧访问者(“脏”)方法,我只能做到这一点,否则他们永远不会达到method_missing方法。

所以经过了很多挣扎的我钻进了以下解决方案(适应你的情况下):

class Account < ActiveRecord::Base 

    # ... 

    unless attribute_methods_generated? 
    define_attribute_methods 
    undef_method "password" 
    undef_method "password=" 
    end 

    # ... 

end 

我只需要去除读者和writter方法,但你可以删除其他所有“脏“方法,如果你想。如果已经产生的脏方法

attribute_methods_generated?类方法返回true。然后它强制生成脏方法,然后它删除所需的方法。如果您简单地尝试直接在类作用域中使用undef_method方法,它会抛出一个异常,告诉您您尝试删除的方法不存在(还)。这就是为什么你需要在上面这样做。