Rails - 对于JSON Devise请求,“WARNING: Can't verify CSRF token authenticity”警告

83
如何获取CSRF令牌以在JSON请求中传递?
我知道出于安全考虑,Rails检查所有请求类型(包括JSON/XML)的CSRF令牌
我可以在控制器中加入skip_before_filter :verify_authenticity_token,但是我将失去CRSF防护(不建议使用 :-) )。
这个类似的(尚未被接受的)答案建议:

使用<%= form_authenticity_token %>检索令牌。

问题是如何操作?我需要先调用任何一个页面来检索令牌,然后使用Devise进行真正的身份验证吗?还是说它只是一次性信息,我可以从我的服务器获取并一致地使用它(直到我在服务器上手动更改它)?
10个回答

131

编辑:

在Rails 4中,我现在使用下面@genkilabs在评论中建议的方法:

protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }

这个方法不会完全关闭内置安全性,而是在没有CSRF令牌的情况下杀死任何可能存在的会话。


skip_before_filter: verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }

这将为已正确标记为此类的JSON post/put关闭CSRF检查。

例如,在iOS中,将以下内容设置为您的NSURLRequest中的"parameters"参数:


[request setHTTPMethod:@"POST"];

[request setValue:@"application/json" 
       forHTTPHeaderField:@"content-type"];

[request setValue:@"application/json" 
       forHTTPHeaderField:@"accept"];

[request setHTTPBody:[NSData dataWithBytes:[parameters UTF8String] 
                                            length:[parameters length]]];

3
如果攻击者将格式标记为“json”,您在执行此操作时就会受到攻击。这就是Rails发出警告的原因。我希望我的JSON能够正确传递令牌,否则日志中出现警告也没关系。 - user1027503
22
当关闭过滤器时,您可能会受到攻击,因此建议您采取其他安全措施。通常,对于API请求,请求者会将API密钥与POST数据一起发送,然后在执行所需方法之前进行验证。 - Ryan Crews
20
在 Rails 4 中,您可以这样做:protect_from_forgery with: :null_session, :if => Proc.new { |c| c.request.format == 'application/json' } - genkilabs
2
@genkilabs 我喜欢这个,已经添加到针对Rails 4用户的答案中了,谢谢 =] - Ryan Crews
1
该语句表示如果请求未通过verify_authenticity_token方法,则Rails将不会通过cookie数据获取会话,而是为请求创建一个新会话。请求将是NullSessionHash的实例,因此为null_session。此外,如果您不想进行精确匹配,请使用以下内容。这将处理其他类似的内容类型:protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format =~ %r{application/json} } - Jimmy Shaw
显示剩余5条评论

19

在成功登录后,您可以使用自定义标头发送CSRF令牌。

例如,在sessions#create中添加以下内容:

response.headers['X-CSRF-Token'] = form_authenticity_token

示例登录响应头提供CSRF令牌:

HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Connection: Keep-Alive
Content-Length: 35
Content-Type: application/json; charset=utf-8
Date: Mon, 22 Oct 2012 11:39:04 GMT
Etag: "9d719d3b9aabd413c3603e04e8a3933d"
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-10-12)
Set-Cookie: [cut for readability] 
X-Csrf-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=
X-Request-Id: 178746992d7aca928c876818fcdd4c96
X-Runtime: 0.169792
X-Ua-Compatible: IE=Edge

此令牌有效期到下次登录或(如果您通过API支持此功能)注销。

您的客户端可以从登录响应头中提取并存储令牌。然后,每个POST/PUT/DELETE请求都必须使用在登录时收到的值设置X-CSRF-Token头。

带有CSRF令牌的示例POST头:

POST /api/report HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Type: application/json; charset=utf-8
Cookie: [cut for readability]
Host: localhost:3000
User-Agent: HTTPie/0.3.0
X-CSRF-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=

文档:form_authenticity_token


18

确实是最简单的方法。不要费心改变标题。

确保你有:

<%= csrf_meta_tag %>

在您的layouts/application.html.erb文件中,只需添加以下代码来创建隐藏的输入字段:

<input name="authenticity_token" 
       type="hidden" 
       value="<%= form_authenticity_token %>"/>

或者如果你想要使用 jQuery 的 ajax 方法进行 POST 请求:

$.ajax({     
    type: 'POST',
    url: "<%= someregistration_path %>",
    data: { "firstname": "text_data_1", "last_name": "text_data2", "authenticity_token": "<%= form_authenticity_token %>" },                                                                                  
    error: function( xhr ){ 
      alert("ERROR ON SUBMIT");
    },
    success: function( data ){ 
      //data response can contain what we want here...
      console.log("SUCCESS, data="+data);
    }
});

实际上,当您发布JSON数据时,只需将有效的authenticity_token字段添加到post数据中,警告就会消失...


在我看来,它不仅应该在日志记录器中放置警告,而且默认情况下应完全丢弃没有有效csrf标记的帖子,因为它只是发布警告并正常提交数据... - Walter Schreppers
在输入隐藏的真实性令牌字段方面,更简短的方法是= token_tag(nil)(nil很重要)。 - Yo Ludke
在旧版本的Rails中,标签以复数形式结束。 <%= csrf_meta_tags %> - cyonder

4

我不确定你是否想要这样做 - 浏览器可以使用JSON API。 - Joe Van Dyk
谢谢!我需要一个快速(而且不太规范)的方法来解决这个问题,这可能足以满足我想要实现的目标。 - NerdyTherapist
链接文档(现已更新为Rails 5)似乎是:protect_from_forgery with: :exception, unless: -> { request.format.json? },对我很有效。谢谢。 - akostadinov
1
这只是禁用了检查,从而避免了这个问题。如果一个应用程序要禁用此功能,那么开启防伪保护还有什么意义呢? - jefflunt

3
值得担忧的是,在 Rails 3.2.3 中,我们现在会在 production.log 中收到 CSRF 警告,但是帖子不会失败!我希望它能够失败,因为这可以保护我免受攻击。而且你可以在 before 过滤器中使用 jQuery 添加 csrf token: http://jasoncodes.com/posts/rails-csrf-vulnerability

2
我已经使用了以下代码。使用include?方法,因此如果内容类型为application/json;charset=utf-8,则仍然有效。
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format.include? 'application/json' }

2
这个答案 更好。您可以在任何XMLHttpRequest发送之前追加CSRF-TOKEN验证,而无需额外的努力。不需要使用JQuery或其他内容,只需复制/粘贴并刷新即可。只需简单地添加以下代码。
(function() {
    var send = XMLHttpRequest.prototype.send,
        token = $('meta[name=csrf-token]').attr('content');
    XMLHttpRequest.prototype.send = function(data) {
        this.setRequestHeader('X-CSRF-Token', token);
        return send.apply(this, arguments);
    };
}());

1

我在使用以下版本的Rails时遇到了同样的问题:
gem 'rails',:git => 'git://github.com/rails/rails.git',:branch => '3-2-stable'

我升级到了3.2.2版本,现在一切都正常了。 :)
gem 'rails','3.2.2'


谢谢您的建议。我尝试了一下,但是我仍然收到相同的警告 WARNING: Can't verify CSRF token authenticity - user1027503

0

同时适用于开发/测试模式。

protect_from_forgery with: :exception unless %w(development test).include? Rails.env

这个警告是因为你正在使用:null_session,在Rails 4.1中,如果没有指定with:选项,默认情况下它可以正常工作。

protect_from_forgery

0
今晚我遇到了同样的问题。 发生这种情况的原因是当您登录时,最后一个csrf-token不再有效。 我所做的是: 在您的app/views/devise/sessions/create.js.rb中添加$("meta[name=csrf-token]").attr('content', '<%= form_authenticity_token %>');。 现在它有一个有效的csrf-token :) 希望能帮到你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接