我真的遇到了这个问题,在这种情况下,我既不想跳过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的回答评论中进行了讨论后,看似令牌在请求时已过期(通过与form_authenticity_token
比较来测试)。这让我更加困惑,因为<%= csrf_meta_tags %>
中设置的令牌在下一个请求进入时已过期。有什么想法吗?
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;
}
}
我验证了响应标头,然后元标记正在被正确重置,但是,当下一个请求进入时,这个新标记再次过期。想法?
答案 0 :(得分:2)
我有同样的问题。我检查了Rails源代码并接下来得出结论:
authenticity_token
本身未过期,因此无需在每次向服务器发出ajax请求后对其进行更新params[:authenticity_token]
和header['x-csrf-token']
,其中只有其中一个,rails会首先检查params
,而不是header
authenticity_token
会有所不同,但这并不重要,因为每次使用一个时间垫生成它并且真正的csrf令牌(在服务器上)与时间无关session[:_csrf_token]
正如你所看到的那样令牌保持在会话中我的问题是我的会话在24小时后过期了(可能是用户在页面上停留了一天而没有刷新)
如果用户通过cookie或其他令牌参数登录,则无论如何他都会获得新会话,并且会生成新的CSRF令牌,并且任何旧authenticity_token
的请求都将无效。
因此,主要问题是会话,它已过期或重置。
答案 1 :(得分: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之前在页面中写一个新的令牌......