【问题标题】:How can I elegantly handle devise's 401 status in AJAX?如何优雅地处理设计在 AJAX 中的 401 状态?
【发布时间】:2012-04-12 13:43:09
【问题描述】:

devise 使用before_filter :authenticate_user! 来限制只有经过身份验证的用户才能访问。

未经身份验证的用户尝试访问受限页面时,devise 会自动导致重定向到登录页面。

因此尝试打开http://localhost:3000/users/edit 将导致重定向到http://localhost:3000/users/sign_in

现在,如果我将链接 http://localhost:3000/users/edit 定义为 :remote => true,devise 只会通过 JS 发出 401 状态代码

我怎样才能优雅地应对这种情况并在叠加层重定向为非远程变体中显示登录对话框?

设计是否为我只需要激活的那种情况提供默认策略?

【问题讨论】:

    标签: ruby-on-rails ajax authentication devise


    【解决方案1】:
    $(document).ajaxError(function (e, xhr, settings) {
            if (xhr.status == 401) {
               $('.selector').html(xhr.responseText);
            }
        });
    

    【讨论】:

    • 这对我有用 - 我把它放在一个 .js 文件中,该文件在我提交 ajax 的每个页面上都会被调用,但我替换了 $('.selector').html(xhr.responseText );} 与 location.reload();这让 Devise 焕然一新,重定向到正常的登录页面,并在登录后让您再次返回
    【解决方案2】:

    这是我现在选择的解决方案(在 CoffeeScript 语法中):

    $ ->
      $("a").bind "ajax:error", (event, jqXHR, ajaxSettings, thrownError) ->
        if jqXHR.status == 401 # thrownError is 'Unauthorized'
          window.location.replace('/users/sign_in')
    

    然而,这(它自己)只是忘记了用户最初想要访问的页面,这限制了可用性。

    更优雅的处理需要额外的(控制器)逻辑。

    更新:正确的重定向

    在函数中,this 保存用户打算访问的初始 URL。

    通过调用window.location.replace(this)而不是显式重定向到登录页面),应用程序将尝试将用户重定向到最初预期的目的地。

    虽然仍然不可能(未经授权),但这现在将是一个 GET 调用(而不是 JS/AJAX)。因此,Devise 能够启动并将用户重定向到登录页面。

    从那时起,Devise 照常运行,在成功登录后将用户转发到最初预期的 URL。

    【讨论】:

    • 改用这个window.location.replace('/auth/login?return_to=' + window.location.pathname);
    【解决方案3】:

    location.reload() 混合事件绑定的版本:

    $(function($) {
      $("#new-user")
        .bind("ajax:error", function(event, xhr, status, error) {
          if (xhr.status == 401) {  // probable Devise timeout
            alert(xhr.responseText);
            location.reload();      // reload whole page so Devise will redirect to signin
          }
        });
    });
    

    使用 Devise 3.1.1 进行测试,这确实正确设置了session["user_return_to"],因此用户在再次登录后返回页面()。

    我添加了alert 作为解决此处讨论的不雅消息问题的简单方法:Session Timeout Message in RoR using Devise

    【讨论】:

    • 我知道这可以作为一种解决超时问题的方法。但是,如果登录表单位于其他页面上,则此代码似乎无法重定向到该页面。
    • 您的意思是登录表单与标准页面位于不同的页面上吗?我没试过。 rake routes | grep signin 会为您返回任何东西吗?我的去devise/sessions#new,我认为这是默认的(尽管我出于其他原因明确定义了它)。这段代码所做的只是重新加载基本页面,这将强制 Devise “重定向,因为非远程变体会这样做。”
    • 问题是,如果用户“当前”发现自己在一个他允许未经身份验证访问的页面上,但点击了一个指向 可以访问的页面的链接需要 他进行身份验证,建议的解决方案只是重新加载当前已可访问的 (!) 页面。意图对 Devise 来说是不可见的,并且 session#new (或类似的)没有受到影响。为了涵盖这两种情况,您应该重定向到预期的页面。请参阅我的回答中的location.replace
    • 啊,我明白你的意思了。是的,你的情况比我最初理解的要复杂。我的应用程序需要对每个页面进行身份验证,因此可以使用来自基本页面的标准设计流程。
    【解决方案4】:

    这是我在 CoffeScript 中的 copy-past-hapy(tm) 解决方案。它将所有 401 重定向到登录页面。

    <% environment.context_class.instance_eval { include Rails.application.routes.url_helpers } %>
    
    $(document).ajaxError (_, xhr)->
      window.location = '<%= new_user_session_path %>' if xhr.status == 401
    

    在 Javascript 中:

    <% environment.context_class.instance_eval { include Rails.application.routes.url_helpers } %>
    
    $(document).ajaxError( function(event, xhr){
      if (xhr.status == 401) {
        window.location = '<%= new_user_session_path %>'
      }
    });
    

    【讨论】:

      【解决方案5】:

      从 Rails 5.1 和新的 rails-ujs 开始,所有自定义事件都只返回一个参数:event。在此参数中,有一个附加属性detail,其中包含一个额外参数数组。参数datastatusxhr 已绑定到event.detail。所以用ajax:error事件处理ajax中的401错误就变成了:

      document.body.addEventListener('ajax:error', function(event) {
        var detail = event.detail;
        var response = detail[0], status = detail[1], xhr = detail[2];
        if (xhr.status == 401) {
          // handle error
        }
      })
      

      【讨论】:

        【解决方案6】:

        如果您正在执行“:remote => true”,则可以在“ajax:error”事件绑定上使用 .live。

        $('#member_invite, #new_user')
                .live("ajax:success", function(evt, data, status, xhr){
                  $.colorbox.close();
                })
                .live("ajax:error", function(evt, data, status, xhr){
                  alert("got an error");
                });
        

        “#new_user”将是表单 ID 值。

        请注意,如果您已经有叠加层或对话框,更优雅的方法是简单地插入一条消息,而不是 alert():

        $('.messages').html('Invalid email or password');
        

        在您的登录表单中,您只需执行一个

        <div class="messages"></div>
        

        或者您甚至可以只替换表单的标题,无论您需要什么。

        【讨论】:

        • 感谢您的建议。我想你明白我在示例中的登录页面。然而,问题是关于在未经身份验证的情况下尝试访问受限区域。请注意,.live() 在 jQuery 1.7 中已被弃用,取而代之的是 .on()
        【解决方案7】:

        我很乐意看看是否也有一种优雅的方法可以做到这一点!

        在那之前,我是这样处理的。

        在您的edit.js.erb视图文件中,您可以输入以下代码:

        <% case response.status
          when 200
        %>
          //do what you need to do
        <% when 401 %>
          //handle the 401 case, for example by redirecting to root or something
          window.location.href('/');
        <% else %>
          //catch all
          alert('We\'ve had a problem, please close this, refresh the page and try again');
        <% end %>
        

        它会查看响应的状态码,如果是 401 则重定向到登录页面。

        我想知道是否没有办法直接在控制器级别处理这个问题。

        【讨论】:

        • 感谢您的想法。它类似于我从这个答案中得到的当前方法:stackoverflow.com/questions/5460150/… --- 但是在这种情况下显然没有 erb 支持,这使得无法进行渲染甚至只是 DRY 重定向到new_user_session_path 或类似的东西。
        猜你喜欢
        • 1970-01-01
        • 2017-02-18
        • 2019-01-15
        • 1970-01-01
        • 1970-01-01
        • 2015-11-19
        • 1970-01-01
        • 1970-01-01
        • 2016-03-23
        相关资源
        最近更新 更多