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

mat*_*att 7 javascript ajax ruby-on-rails csrf ruby-on-rails-4

我真的遇到了麻烦,在这种情况下,我既不想跳过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);
Run Code Online (Sandbox Code Playgroud)

并在我的控制器中插入一个断点,如下所示:

def verify_authenticity_token
  binding.pry
  super
end
Run Code Online (Sandbox Code Playgroud)

我已验证标头已设置:

[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",
   .
   .
   .
  }
Run Code Online (Sandbox Code Playgroud)

我还尝试将令牌作为带有密钥的参数传递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
Run Code Online (Sandbox Code Playgroud)

有人见过这个吗?有什么可能导致这种情况的想法吗?

提前致谢!

编辑:

在marflar的回答的评论中进行讨论后,看起来令牌在请求发生时已过期(通过比较测试form_authenticity_token).这让我更加困惑,因为<%= csrf_meta_tags %>当下一个请求进入时,设置的令牌已过期.有什么想法吗?

编辑2:按照下面的marflar的建议,我将以下内容添加after_filter到我的app控制器:

def set_csrf_headers
  response.headers['X-CSRF-Param'] = request_forgery_protection_token.to_s
  response.headers['X-CSRF-Token'] = form_authenticity_token
end
Run Code Online (Sandbox Code Playgroud)

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

ste*_*och 1

我的猜测是 Rails 可能期望令牌位于 HTML 中,而不是标头中。你能尝试一下吗?希望能帮助到你。

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

我通常在控制器操作中将其设置如下:

response.headers['X-CSRF-Param'] = "#{request_forgery_protection_token}"
response.headers['X-CSRF-Token'] = "#{form_authenticity_token}"
Run Code Online (Sandbox Code Playgroud)

您页面中的令牌是否与调用返回的令牌匹配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);
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

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