即使设置了标头,也无法验证 CSRF 令牌的真实性 Rails 4 Ajax

Posted

技术标签:

【中文标题】即使设置了标头,也无法验证 CSRF 令牌的真实性 Rails 4 Ajax【英文标题】:Can't verify CSRF token authenticity Rails 4 Ajax even when header is set 【发布时间】:2014-12-14 04:48:36 【问题描述】:

我真的遇到了麻烦,在这种情况下,我既不想跳过verify_authenticity_token 过滤器,也不想更改为protect_from_forgery with: :null_session

在我的请求方法中,我设置了一个带有 csrf 令牌的标头,如下所示:

var token = document.querySelector("meta[name='csrf-token']").content;
xhr.setRequestHeader("X-CSRF-Token", token);

然后像这样在我的控制器中插入断点:

def verify_authenticity_token
  binding.pry
  super
end

我已验证已设置标题:

[1] pry(#<MyController>)> request.headers
=> #<ActionDispatch::Http::Headers:0x007fb227cbf490
 @env=
  "CONTENT_LENGTH"=>"202",
   .
   .
   .
   # omitted headers
   .
   .
   .
   "HTTP_X_CSRF_TOKEN"=>"the-correct-token-from-meta-tag",
   .
   .
   .
  

我还尝试将令牌作为参数传递给带有键 authenticity_token 的参数(就像在 Rails 表单中所做的那样),并将 X-CSRF-Param 标记设置为匹配(来自 meta[name="csrf-param"])。

但我仍然得到:

Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 14638ms

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken

有人见过这个吗?关于可能导致这种情况的任何想法?

提前致谢!

编辑:

在 marflar 的答案的 cmets 中进行讨论后,发出请求时令牌似乎已过期(通过与 form_authenticity_token 比较进行测试)。这让我更加困惑,因为&lt;%= csrf_meta_tags %&gt; 中设置的令牌在下一个请求进入时已过期。有什么想法吗?

EDIT2:按照以下 marflar 的建议,我将以下 after_filter 添加到我的应用控制器:

def set_csrf_headers
  response.headers['X-CSRF-Param'] = request_forgery_protection_token.to_s
  response.headers['X-CSRF-Token'] = form_authenticity_token
end

我在请求方法中更新了xhr.onload,如下:

namespace.request = = function (type, url, opts, callback) 

// code omitted

  xhr.onload = function () 
    setCSRFHeaders(xhr);
    var res = data: JSON.parse(xhr.response), status: xhr.status;
    return callback.call(xhr, null, res);
  ;

// code omitted



function setCSRFHeaders ( xhr ) 
  var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
  var csrf_token = xhr.getResponseHeader('X-CSRF-Token');

  if (csrf_param) 
    document.querySelector("meta[name='csrf-param']").content = csrf_param;
  
  if (csrf_token) 
    document.querySelector("meta[name='csrf-token']").content = csrf_token;
  

我验证了响应标头,然后元标记被正确重置,但是,当下一个请求进入时,这个新令牌再次过期。想法?

【问题讨论】:

【参考方案1】:

我的猜测是 Rails 可能希望令牌在 html 中,而不是在标题中。你能试试吗? 希望对您有所帮助。

实际上,我认为您可能正在使用陈旧的 CRSF 令牌,因为您是从模板中复制它。

我通常在控制器操作中这样设置:

response.headers['X-CSRF-Param'] = "#request_forgery_protection_token"
response.headers['X-CSRF-Token'] = "#form_authenticity_token"

您页面中的令牌是否与调用 form_authenticity_token 返回的令牌匹配?

更新

回应您的评论(引述如下):

我刚刚检查过,你说得对,它是一个过时的令牌,不幸的是,这让我更加困惑。带有 CSRF 数据的元标记是在初始页面加载时设置的,此时它们与 form_authenticity_token 匹配,但在发出第一个 ajax 请求时,该标记已过时。因此,无论我将它们设置在 HTML 中还是作为标头都无关紧要,因为这会同时发生,因此在发出下一个请求之前令牌过期会遇到相同的问题。感谢您迄今为止的帮助——这里有什么想法吗?

我在实现 AJAX 登录时遇到了这类问题。我发现登录后我无法发出任何类型的 POST 请求,并且我需要以下代码来刷新我的令牌:

var update_csrf_token_and_param_after_ajax_login = function() 
  $(document).on("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);
    
  );

我认为您可能只需要在执行 POST 之前将一个新令牌写入您的页面......

【讨论】:

我不确定我理解你所说的“在 HTML 中”是什么意思——你能给我举个例子吗? @fancycoder 我更新了我的答案,我想知道您是否使用过时的令牌 我刚查了一下,你说得对,它是一个过时的令牌,不幸的是,这让我更加困惑。带有 CSRF 数据的元标记是在初始页面加载时设置的,此时它们与 form_authenticity_token 匹配,但在发出第一个 ajax 请求时令牌已过时。因此,无论我将它们设置在 HTML 中还是作为标头都无关紧要,因为这会同时发生,因此在发出下一个请求之前令牌过期会遇到相同的问题。感谢您迄今为止的帮助——这里有什么想法吗? 不幸的是,这并没有做到:/。我用更多信息更新了我的问题。不过感谢您的帮助:)【参考方案2】:

我也有同样的问题。我检查了 Rails 源代码并得出以下结论:

authenticity_token 本身没有过期,所以每次向服务器发出ajax请求后不需要更新它 不需要同时发送params[:authenticity_token]header['x-csrf-token'],只需发送其中一个,rails 将首先检查params,而不是header 在页面刷新时,authenticity_token 会有所不同,但没关系,因为它每次都使用一个 time pad 生成,并且真正的 csrf 令牌(在服务器上)与时间无关 真正的 csrf 令牌保存在 session[:_csrf_token]

您可以看到令牌保留在会话中,我的问题是我的会话在 24 小时后过期(可能用户在页面上停留一天而没有刷新)

如果用户通过 cookie 或其他令牌参数登录,无论如何他都会获得新的会话,并且将生成新的 CSRF 令牌,并且任何带有旧 authenticity_token 的请求都将无效。

所以,主要问题在于会话,它已过期或重置。

【讨论】:

以上是关于即使设置了标头,也无法验证 CSRF 令牌的真实性 Rails 4 Ajax的主要内容,如果未能解决你的问题,请参考以下文章

角度注销后无法验证 CSRF 令牌的真实性

如何生成和验证 csrf 令牌

无法验证 CSRF 令牌真实性 Rails 4.1

Rails post 无法验证 CSRF 令牌的真实性

Rails 3:无法验证 CSRF 令牌的真实性

API/JSON:无法验证 CSRF 令牌的真实性