5

我有一个 Rails 4 应用程序,它使用 Devise 3.4 进行身份验证,我已经定制了禁止用户的能力(使用简单的布尔列users.banned,默认为 false)。该User模型还有一个default_scope只返回非禁止用户。

这就是问题所在 - 我仍然希望我被禁止的用户能够登录,即使他们在登录后不能做任何事情。(他们基本上只是看到一个页面说“你已被禁止”)。但似乎default_scope正在绊倒Devise。当您登录或调用 egauthenticate_user!时,Devise 会尝试使用基本 ActiveRecord 方法之一(如findor )来查找当前用户find_by,但由于它们位于默认范围之外,因此无法找到。因此,Devise 得出用户不存在的结论,并且登录失败。

如何让设计忽略默认范围?

4

1 回答 1

4

在Devise 和 Warden 源代码中挖掘了很长时间后,我终于找到了解决方案。

简短的回答:

将此添加到User类中:

def self.serialize_from_session(key, salt)
  record = to_adapter.klass.unscoped.find(key[0])
  record if record && record.authenticatable_salt == salt
end

(请注意,我只为 ActiveRecord 测试过这个;如果您使用不同的 ORM 适配器,您可能需要更改方法的第一行......但是我不确定其他 ORM 适配器是否甚至有这个概念的“默认所以

长答案:

serialize_from_session从 - 混入 User 类Devise::Models::Authenticatable::ClassMethods。老实说,我不确定它实际上应该做什么,但它是一个公共方法,并且在 Devise API 中记录(非常稀疏),所以我认为它在没有警告的情况下从 Devise 中删除的可能性不大。

这是 Devise 3.4.1 的原始源代码:

def serialize_from_session(key, salt)
  record = to_adapter.get(key)
  record if record && record.authenticatable_salt == salt
end

问题在于to_adapter.get(key)。返回一个环绕类to_adapter的实例,与调用. (Devise 使用gem 来保持它的灵活性;无论您使用 ActiveRecord、Mongoid 还是任何其他与 OrmAdapter 兼容的 ORM,上述方法都无需修改即可工作。)OrmAdapter::ActiveRecordUserto_adapter.getUser.findorm_adapter

但是,当然,User.find只在 default_scope 内搜索,这就是为什么它找不到我的禁止用户。调用直接to_adapter.klass返回User类,然后我可以调用unscoped.find来搜索我的所有用户并使被禁止的用户对设计可见。所以工作线是:

record = to_adapter.klass.unscoped.find(key[0])

请注意,我传递的是 Arraykey[0]而不是key,因为key它是一个 Array(在这种情况下只有一个元素),并且将 Array 传递给find将返回一个 Array,这不是我们想要的。

另请注意,klass在真正的 Devise 源代码中调用将是一个坏主意,因为这意味着您失去了OrmAdapter. 但是在您自己的应用程序中,您可以肯定地知道您正在使用哪个 ORM(Devise 不知道的东西),具体来说是安全的。

于 2015-04-23T04:33:33.930 回答