我们正在尝试扩展Devise(3.1.1)登录/注册方法来处理AJAX请求,但是我们仍然坚持使用可确认的逻辑。通常情况下,如果用户在确认其帐户之前登录设计,他们将被重定向到登录屏幕并显示以下消息:“您必须先确认帐户才能继续。”我们不能弄清楚Devise正在检查确认并做出重定向的决定。
这是我们的扩展 sessions_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友好的方式处理它。但是通过源代码我找不到任何可以进行确认检查的过滤器。可确认的检查在哪里发生,我们如何拦截呢?
答案 0 :(得分:8)
正在发生的事情是sign_in
方法通过抛出一个warden错误来打破你的正常流程,这会调用失败的应用程序。
如果您查看sign_in
中lib/devise/controllers/helpers.rb
的定义,您会看到在第一次登录用户的正常流程中,您最终会调用1} p>
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?
对于尚未确认的confirmable
资源返回false(请参阅lib/devise/models/confirmable.rb:121-127
)。
当我们throw :warden
时,我们最终会调用设计failure_app
。这就是正在发生的事情,以及为什么你要打破控制器的正常控制流程。
(实际上上面是谈论正常的会话控制器流程。我认为你的js
块实际上是多余的 - 调用warden.authenticate!
也会设置用户,所以我认为你扔了在你到达sign_in
之前。)
要回答第二个问题,处理此问题的一种可能方法是创建自己的失败应用。默认设置将warden的failure_app
设置为Devise::Delegator
,这允许您为不同的设计模型指定不同的故障应用程序,但如果未配置任何设备,则默认为Devise::FailureApp
。您可以自定义现有的故障应用程序,通过配置warden将其替换为您自己的故障应用程序,也可以自定义委托者以使用html请求的默认故障应用程序并委托给json的其他故障应用程序。
答案 1 :(得分:3)
@gregates因为搞清楚这一点而获得赞誉,但我想展示我是如何实现这一目标的:
问题是您无法在控制器级别检查或拦截未经证实的用户,因为它会在模型级别使用active_for_authentication?
进行检查,然后立即由Warden的失败应用程序处理。因此,对current_user
的任何引用都会使您正在进行的控制器逻辑短路。解决方案是引入您自己的自定义FailureApp,并根据需要处理事情。在我们的例子中,这意味着为AJAX auth错误引发一个特殊错误。这是我们如何做到的:
首先,创建一个自定义失败应用程序,如下所述:
How To: Redirect to a specific page when the user can not be authenticated · plataformatec/devise Wiki
lib / 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响应。
答案 2 :(得分:2)
转到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
此期间是宽限期,一旦设定,您可以期待
用户可以在注册后自动登录,不再需要重定向到确认页面。
在此期间,可以记住未经证实的用户,并毫无问题地登录。
过期后,未经确认的用户将重定向到确认页面,并且必须在继续之前确认密码。
启用:confirmable
后,我担心宽限期对用户最友好。如果您对此不满意,那么使用:confirmable
:
我刚刚注意到你在阅读Rich Peck的评论时使用了JSON响应。我建议你不要使用JSON响应,而是使用Ajax / Plain JS表单作为登录表单的简单方法,并期望Devise进行正常的页面重定向。原因是如果使用JSON响应会有太多资源需要更新,例如,csrf_token,cookie,现有授权等。请参阅我关于类似主题的其他答案:https://stackoverflow.com/a/19538974/1721198
答案 3 :(得分:1)
要添加到@gregates答案,请尝试覆盖用户(资源)模型中的方法active_for_authentication?
,如下所示:
def active_for_authentication?
true
end
如果您能够登录,则问题必须与会话控制器有关。确保你没有错过任何明显的东西,比如指定到新会话控制器的路线