当 Devise 用户超出我的默认范围时,如何允许他们登录?

GMA*_*GMA 5 activerecord ruby-on-rails default-scope devise warden

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

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

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

GMA*_*GMA 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
Run Code Online (Sandbox Code Playgroud)

(请注意,我只针对 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
Run Code Online (Sandbox Code Playgroud)

问题出在to_adapter.get(key). to_adapter返回一个OrmAdapter::ActiveRecord包裹在该类中的实例Userto_adapter.get本质上与调用 相同User.find。(Devise 使用orm_adaptergem 来保持灵活性;无论您使用的是 ActiveRecord、Mongoid 还是任何其他 OrmAdapter 兼容的 ORM,上述方法都无需修改即可工作。)

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

record = to_adapter.klass.unscoped.find(key[0])
Run Code Online (Sandbox Code Playgroud)

请注意,我传递的是 ,key[0]而不是key,因为key是 一个数组(在本例中只有一个元素),并且将一个数组传递给find将返回一个数组,这不是我们想要的。

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