轨,设计验证,CSRF问题(Rails, Devise authentication, CSRF i

2019-06-17 16:29发布

我正在使用Rails一燎页的申请书。 当进出设计控制器签约使用AJAX调用。 我得到的问题是,当我1)2号)退出后重新登录不起作用。

我认为它的令牌相关CSRF它得到复位,当我退出(尽管它不应该据我所知),并因为它的单页上,老CSRF令牌正在XHR请求发送从而复位会话。

更具体的是这样的工作流程:

  1. 登入
  2. 登出
  3. 登录(成功201但是打印WARNING: Can't verify CSRF token authenticity在服务器日志)
  4. 随后的AJAX请求失败401未授权
  5. 刷新网站(在这一点上,CSRF页标题更改为别的东西)
  6. 我可以登录,它的工作原理,直到我尝试退出并重新。

任何线索非常感谢! 让我知道如果我可以添加任何更多的细节。

Answer 1:

神保做了一个真棒的工作,解释了“为什么”的问题背后的你正在运行之中。 有可以采取的解决问题的两种方法:

  1. (所推荐的神保)重写设计:: SessionsController回报新CSRF令牌:

     class SessionsController < Devise::SessionsController def destroy # Assumes only JSON requests signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) render :json => { 'csrfParam' => request_forgery_protection_token, 'csrfToken' => form_authenticity_token } end end 

    并创建一个成功的处理程序在客户端的请求SIGN_OUT(可能需要根据您的设置一些调整,比如GET VS DELETE):

     signOut: function() { var params = { dataType: "json", type: "GET", url: this.urlRoot + "/sign_out.json" }; var self = this; return $.ajax(params).done(function(data) { self.set("csrf-token", data.csrfToken); self.unset("user"); }); } 

    这也假设你有像这样的东西所有的AJAX请求令牌自动包括CSRF:

     $(document).ajaxSend(function (e, xhr, options) { xhr.setRequestHeader("X-CSRF-Token", MyApp.session.get("csrf-token")); }); 
  2. 更简单地说,它是否适合你的应用程序,你可以简单地覆盖Devise::SessionsController并覆盖与令牌检查skip_before_filter :verify_authenticity_token



Answer 2:

我刚刚碰到这个问题为好。 有很多怎么回事。

TL; DR -的失败原因是,CSRF令牌与您的服务器会话相关联(你有一个服务器会话是否你登录或注销)。 该CSRF令牌在DOM包括您在每一页上加载页面。 在注销,您的会话被重置,并没有CSRF令牌。 通常情况下,注销重定向到不同的页面/动作,它给你一个新的CSRF令牌,但因为你正在使用AJAX,你需要手动完成。

  • 你需要重写设计SessionController ::销毁方法返回新的CSRF令牌。
  • 然后在客户端,你需要设置一个成功处理程序为您注销的XMLHttpRequest。 在该处理程序,你需要从响应利用此新CSRF令牌,并设置它在你的DOM: $('meta[name="csrf-token"]').attr('content', <NEW_CSRF_TOKEN>)

更详细的解释你最有可能得到protect_from_forgery在ApplicationController.rb文件从所有其他控制器继承设置(这是很常见的,我认为)。 protect_from_forgery所有非GET HTML / JavaScript的请求执行CSRF检查。 由于设计登录是POST,它执行CSRF检查。 如果一个CSRF检查失败,则用户的当前会话将被清除,即注销用户,因为该服务器假定它是一个攻击(这是正确的/预期的行为)。

因此,假如你在登出状态开始,你做一个新的页面加载,并且不会再重新加载页面:

  1. 在渲染页面:服务器插入与您的服务器会话到页面相关的CSRF令牌。 您可以通过运行从JavaScript控制台下面的浏览器中查看该令牌$('meta[name="csrf-token"]').attr('content')

  2. 然后,您登录通过一个XMLHttpRequest:您的CSRF令牌保持不变在这一点上让CSRF令牌在会话仍然匹配插入到页面中的一个。 在幕后,在客户端,jQuery的UJS正在监听XHR的,并设置一个“X-CSRF令牌”标头的值$('meta[name="csrf-token"]').attr('content')会自动为您(请记住,这是由服务器在步骤1中的CSRF令牌集)。 服务器比较由jQuery的UJS的头和存储在您的会话信息的一个令牌集与它们匹配这样的请求成功。

  3. 然后,通过一个XMLHttpRequest注销:这将重置会话,为您提供了一个新的会话没有CSRF令牌。

  4. 你然后再次登录通过一个XMLHttpRequest:jQuery的UJS是直接从价值的CSRF令牌$('meta[name="csrf-token"]').attr('content') 该值仍然是的CSRF令牌。 它采用这个古老的令牌,并使用它来设置“X-CSRF令牌”。 服务器用一个新的CSRF令牌这个头值,并将其添加到您的会话,这是不同的。 这种差异导致protect_form_forgery失败,这引发了WARNING: Can't verify CSRF token authenticity ,并重置您的会话,其注销用户。

  5. 然后你让一个需要登录的用户的另一个的XMLHttpRequest:当前会话没有登录的用户,以便制定返回401。

更新:8/14设计注销不给你一个新的CSRF令牌,那么通常发生后注销给你一个新的CSRF令牌重定向。



Answer 3:

我的回答来自@Jimbo和@Sija大量借鉴,但是我使用的色器件/ angularjs会议建议在Rails的CSRF保护+ Angular.js:protect_from_forgery让我退出的POST ,并阐述了一点在我的博客时,我原本这样做。 这有应用控制器设置Cookie的CSRF上的方法:

after_filter  :set_csrf_cookie_for_ng

def set_csrf_cookie_for_ng
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

所以我使用@传送特性Sija的格式,但使用的代码从早期做方案,给我:

class SessionsController < Devise::SessionsController
  after_filter :set_csrf_headers, only: [:create, :destroy]

  protected
  def set_csrf_headers
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?  
  end
end

为了完整起见,因为我花了一两分钟,去解决问题,我也注意到,有必要修改配置/ routes.rb文件,宣布你已经覆盖了会话控制器。 就像是:

devise_for :users, :controllers => {sessions: 'sessions'}

这也是一个大的CSRF清理,我已经在我的应用程序完成的,这可能是有趣的,别人的一部分。 该博客文章在这里 ,其他的变化包括:

从ActionController的:: InvalidAuthenticityToken,这意味着,如果事情不同步的应用程序将修复本身,而不是需要用户清除Cookie抢救。 如往年一样在轨道,我认为你的应用程序控制器将被默认:

protect_from_forgery with: :exception

在这种情况下,你再需要:

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  render :error => 'invalid token', {:status => :unprocessable_entity}
end

我也有一些悲伤与种族条件,在设计的timeoutable模块,我已经在博客中谈到进一步的某些交互 - 总之,你应该考虑使用active_record_store而不是cookie_store,选择要谨慎发放并行请求附近sign_in和SIGN_OUT行动。



Answer 4:

这是我的看法:

class SessionsController < Devise::SessionsController
  after_filter :set_csrf_headers, only: [:create, :destroy]
  respond_to :json

  protected
  def set_csrf_headers
    if request.xhr?
      response.headers['X-CSRF-Param'] = request_forgery_protection_token
      response.headers['X-CSRF-Token'] = form_authenticity_token
    end
  end
end

而在客户端:

$(document).ajaxComplete(function(event, xhr, settings) {
  var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
  var csrf_token = xhr.getResponseHeader('X-CSRF-Token');

  if (csrf_param) {
    $('meta[name="csrf-param"]').attr('content', csrf_param);
  }
  if (csrf_token) {
    $('meta[name="csrf-token"]').attr('content', csrf_token);
  }
});

这将让您的CSRF meta标签更新每次返回时X-CSRF-TokenX-CSRF-Param通过AJAX请求头。



Answer 5:

挖监狱长源后,我注意到,设置sign_out_all_scopesfalse的清算整个会话停止监狱长,所以CSRF令牌标志奏之间保留。

在设计问题装钉相关讨论: https://github.com/plataformatec/devise/issues/2200



Answer 6:

我只是在我的布局文件中添加这和它的工作

    <%= csrf_meta_tag %>

    <%= javascript_tag do %>
      jQuery(document).ajaxSend(function(e, xhr, options) {
       var token = jQuery("meta[name='csrf-token']").attr("content");
        xhr.setRequestHeader("X-CSRF-Token", token);
      });
    <% end %>


Answer 7:

检查您是否已经在你的application.js文件中包含该

// =需要的jQuery

// =需要jquery_ujs

是的原因是jQuery的轨道宝石在默认情况下会自动将所有Ajax请求的CSRF令牌,需要这两个



Answer 8:

在我的情况后,登陆用户,我需要重新绘制用户的菜单。 这工作,但我得到的每一个请求到服务器CSRF真实性错误,在相同的部分(而无需刷新页面,当然)。 因为我需要渲染的js鉴于上述解决方案是行不通的。

我所做的就是这个,用设计:

应用程序/控制器/ sessions_controller.rb

   class SessionsController < Devise::SessionsController
      respond_to :json

      # GET /resource/sign_in
      def new
        self.resource = resource_class.new(sign_in_params)
        clean_up_passwords(resource)
        yield resource if block_given?
        if request.format.json?
          markup = render_to_string :template => "devise/sessions/popup_login", :layout => false
          render :json => { :data => markup }.to_json
        else
          respond_with(resource, serialize_options(resource))
        end
      end

      # POST /resource/sign_in
      def create
        if request.format.json?
          self.resource = warden.authenticate(auth_options)
          if resource.nil?
            return render json: {status: 'error', message: 'invalid username or password'}
          end
          sign_in(resource_name, resource)
          render json: {status: 'success', message: '¡User authenticated!'}
        else
          self.resource = warden.authenticate!(auth_options)
          set_flash_message(:notice, :signed_in)
          sign_in(resource_name, resource)
          yield resource if block_given?
          respond_with resource, location: after_sign_in_path_for(resource)
        end
      end

    end

从那以后,我给予该重绘菜单控制器#行动的请求。 而在JavaScript中,我修改了X-CSRF-帕拉姆和X-CSRF令牌:

应用程序/视图/实用程序/ redraw_user_menu.js.erb

  $('.js-user-menu').html('');
  $('.js-user-menu').append('<%= escape_javascript(render partial: 'shared/user_name_and_icon') %>');
  $('meta[name="csrf-param"]').attr('content', '<%= request_forgery_protection_token.to_s %>');
  $('meta[name="csrf-token"]').attr('content', '<%= form_authenticity_token %>');

我希望这是有用的人在同一个JS的情况:)



Answer 9:

在回复@ sixty4bit的评论; 如果你遇到这样的错误:

Unexpected error while processing request: undefined method each for :authenticity_token:Symbol` 

更换

response.headers['X-CSRF-Param'] = request_forgery_protection_token

response.headers['X-CSRF-Param'] = request_forgery_protection_token.to_s


文章来源: Rails, Devise authentication, CSRF issue