6

我们正在尝试扩展 Devise (3.1.1) 登录/注册方法来处理 AJAX 请求,但被可确认的逻辑卡住了。通常,如果用户在确认他们的帐户之前登录到 Devise,他们将被重定向到登录屏幕,并显示闪烁消息:“您必须在继续之前确认您的帐户。” 我们无法弄清楚 Devise 在哪里检查确认并做出重定向的决定。

这是我们扩展的session_controller代码。它适用于成功和失败的登录尝试:

  # CUSTOM (Mix of actual devise controller method and ajax customization from http://natashatherobot.com/devise-rails-sign-in/):
  def create
    # (NOTE: If user is not confirmed, execution will never get this far...)
    respond_to do |format|
      format.html do 
        # Copied from original create method:
        self.resource = warden.authenticate!(auth_options)
        set_flash_message(:notice, :signed_in) if is_navigational_format?
        sign_in(resource_name, resource)
        respond_with resource, :location => after_sign_in_path_for(resource)
      end                       
      format.js  do
        # Derived from Natasha AJAX recipe:
        self.resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
        sign_in(resource_name, resource)
        return render :json => {:success => true, :token => form_authenticity_token() }, content_type: "application/json" # Need to explicitely set content type to JSON, otherwise it gets set as application/javascript and success handler never gets hit.
      end          
    end

  end

  def failure
    return render :json => {:success => false, :errors => ["Login failed."]}
  end

问题是,如果用户未经确认,则该create方法永远不会受到打击。重定向发生在之前的某个地方,这意味着我们无法以 JS 友好的方式处理它。但是通过查看源代码,我找不到任何可以进行可确认检查的过滤器。可确认的检查发生在哪里,我们如何拦截它?

4

4 回答 4

8

发生的事情是该sign_in方法通过抛出一个守望者错误使您脱离正常流程,这将调用失败的应用程序。

如果您查看sign_inin的定义lib/devise/controllers/helpers.rb,您会发现在您首次登录用户的正常流程中,您最终会调用

warden.set_user(resource, options.merge!(:scope => scope)

warden是对Warden::Proxy对象的引用,如果您查看它的set_user作用(您可以在 处看到warden/lib/warden/proxy.rb:157-182),您会发现在将用户序列化到会话中之后,它会运行任何after_set_user回调。

Devise 在 中定义了这些lib/devise/hooks/,而我们感兴趣的特定是lib/devise/hooks/activatable.rb

Warden::Manager.after_set_user do |record, warden, options|
  if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication?
    scope = options[:scope]
    warden.logout(scope)
    throw :warden, :scope => scope, :message => record.inactive_message
  end
end

如您所见,如果记录不是active_for_authentication?,那么我们throw。这就是在您的情况下发生的情况 -为尚未确认active_for_authentication?的资源返回 false (请参阅 参考资料)。confirmablelib/devise/models/confirmable.rb:121-127

当我们throw :warden最终调用该设计时failure_app。这就是正在发生的事情,以及为什么您要打破控制器的正常控制流程。

(实际上上面是在谈论正常的会话控制器流程。我认为你的js块实际上是多余warden.authenticate!的——调用也会设置用户,所以我认为你甚至在到达之前就扔了sign_in。)

要回答您的第二个问题,处理此问题的一种可能方法是创建您自己的故障应用程序。默认情况下,devise 将warden 设置failure_appDevise::Delegator,这允许您为不同的设计模型指定不同的失败应用程序,但Devise::FailureApp如果未配置任何内容,则默认为。您可以自定义现有的失败应用程序,通过配置守望者将其替换为您自己的失败应用程序,或者您可以自定义委托器以将默认失败应用程序用于 html 请求并委托给不同的失败应用程序以获取 json。

于 2013-10-31T14:08:36.757 回答
3

我以前遇到过同样的问题。其实可以很简单的解决。

转到config/initializers/devise.rb,找到以下行

# ==> Configuration for :confirmable
# A period that the user is allowed to access the website even without
# confirming his account. For instance, if set to 2.days, the user will be
# able to access the website for two days without confirming his account,
# access will be blocked just in the third day. Default is 0.days, meaning
# the user cannot access the website without confirming his account.
config.allow_unconfirmed_access_for = 0.days

激活最后一行并将期间设置为您喜欢的时间,例如7.days

此期限为宽限期,一旦设置,您可以期待

  1. 用户注册后可以自动登录,不再重定向到确认页面。

  2. 在此期间,未确认的用户可以被记住,并且登录没有问题。

  3. 期限到期后,未确认的用户将被重定向到确认页面,并且必须在继续之前确认密码。

启用后,:confirmable恐怕宽限期是最用户友好的。如果您对此甚至不满意,那就没有什么可使用的了:confirmable:)

关于 JSON 响应的旁注

我刚刚注意到您在阅读 Rich Peck 的评论时使用了 JSON 响应。我建议您不要使用 JSON 响应,而是使用 Ajax/Plain JS 表单作为登录表单的更简单方法,并期望 Devise 进行正常的页面重定向。原因是如果使用 JSON 响应,将有太多资源需要更新,例如 csrf_token、cookie、现有授权等。请参阅我关于类似主题的其他答案:https ://stackoverflow.com/a/19538974/1721198

于 2013-11-04T17:15:59.270 回答
3

@gregates 因弄清楚这一点而受到赞誉,但我想展示我是如何真正做到这一点的:

问题是您无法在控制器级别检查或拦截未经确认的用户,因为它在模型级别使用 进行检查active_for_authentication?,然后立即由 Warden 的故障应用程序处理。因此,任何引用current_user都会使您正在进行的任何控制器逻辑短路。解决方案是引入您自己的自定义 FailureApp,并按照您认为合适的方式处理事情。在我们的例子中,这意味着为 AJAX 身份验证错误引发一个特殊错误。我们是这样做的:

首先,创建一个自定义故障应用程序,如下所述:
如何:当用户无法通过身份验证时重定向到特定页面 · plataformatec/devise Wiki

库/custom_failure.rb:

class CustomFailure < Devise::FailureApp

  def respond

    # We override Devise's handling to throw our own custom errors on AJAX auth failures,
    # because Devise provides no easy way to deal with them:

    if request.xhr? && warden_message == :unconfirmed
      raise MyCustomErrors::AjaxAuthError.new("Confirmation required. Check your inbox.")
    end

    # Original code:

    if http_auth?
      http_auth
    elsif warden_options[:recall]
      recall
    else
      redirect
    end
  end

end

然后告诉 Warden 使用自定义故障应用程序:

配置/初始化程序/devise.rb:

  config.warden do |manager|
    manager.failure_app = CustomFailure
  end

然后你就可以随心所欲地处理你的自定义错误。在我们的例子中,我们让 ErrorController 返回 401 JSON 响应。

于 2013-11-09T17:24:54.973 回答
1

要添加到@gregates 答案,请尝试覆盖active_for_authentication?User(resource) 模型中的方法,如下所示:

def active_for_authentication?
  true
end

如果您能够登录,那么问题必须与您的会话控制器有关。确保你没有错过任何明显的东西,比如指定新会话控制器的路由

于 2013-11-06T23:00:32.797 回答