Rails:发出POST请求时无法验证CSRF令牌的真实性


89

我想对POST request本地开发人员进行这样的操作:

  HTTParty.post('http://localhost:3000/fetch_heroku',
                :body => {:type => 'product'},)

但是,它会从服务器控制台报告

Started POST "/fetch_heroku" for 127.0.0.1 at 2016-02-03 23:33:39 +0800
  ActiveRecord::SchemaMigration Load (0.0ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by AdminController#fetch_heroku as */*
  Parameters: {"type"=>"product"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 1ms

这是我的控制器和路由设置,非常简单。

  def fetch_heroku
    if params[:type] == 'product'
      flash[:alert] = 'Fetch Product From Heroku'
      Heroku.get_product
    end
  end

  post 'fetch_heroku' => 'admin#fetch_heroku'

我不确定该怎么做?关闭CSRF当然可以,但是我认为在创建这样的API时应该是我的错误。

我还有其他需要设置的设置吗?


5
对于API,通常可以关闭CSRF令牌验证。我用protect_from_forgery with: :null_session
dcestari

Answers:


110

跨站点请求伪造(CSRF / XSRF)是指恶意网页诱使用户执行不想要的请求,例如,通过使用小书签,iframe或仅创建外观上足以使用户愚蠢的页面。

Rails的CSRF保护作出了“经典” Web应用程序-它只是给出了一个程度的保证了请求源于自己的Web应用程序。CSRF令牌的工作原理就像只有服务器知道的秘密一样-Rails会生成一个随机令牌并将其存储在会话中。您的表单通过隐藏的输入发送令牌,Rails会验证任何非GET请求都包括与会话中存储的令牌匹配的令牌。

但是,API通常是跨站点定义的,并且打算在您的Web应用程序之外使用,这意味着CSRF的整个概念并不完全适用。

相反,您应使用基于令牌的策略来通过API密钥和机密对API请求进行身份验证,因为您正在验证请求是否来自经过批准的API客户端,而不是您自己的应用。

您可以停用@dcestari指出的CSRF:

class ApiController < ActionController::Base
  protect_from_forgery with: :null_session
end

更新。在Rails 5中,可以使用以下--api选项生成仅API应用程序:

rails new appname --api

它们不包括CSRF中间件和许多其他的超级组件。


谢谢,我选择关闭CSRF关闭的一部分:stackoverflow.com/questions/5669322/...
cqcn1991

4
这表明所有API服务于应用程序到应用程序之间的通信(在其中向单个伙伴发出唯一的密钥和机密)。在那种情况下,就像服务器到服务器的通信一样,这个答案是合适的。但是,对于大多数Web应用程序开发人员而言,令人困惑的是,对于由您控制和编写的Javascript客户端,您不想使用单个密钥秘密(将单个密钥秘密暴露给所有客户端)。相反,如果您在每次请求时都将CSRF令牌传递回Rails,那么Rail的CSRF和cookie会话机制就可以很好地工作,即使对于使用您API的Javascript应用程序也是如此。
詹森FB,

你在AJAX做到这一点的方法是在这个SO岗位描述stackoverflow.com/questions/7203304/...
贾森FB

@JasonFB您是正确的,因为使用JavaScript客户端的单个机密不起作用。但是,如果您打算构建还可以被其他类型的非浏览器客户端使用的API,则使用会话和Rails CSRF保护仍然存在问题。
最高

如果您提供或建立CSRF的替代方案,请成为我的客人。只需知道要更换的原因,就可以知道用什么替代它。显然,现在我们的疑问变得与上下文有关。
Jason FB

77

关闭不会呈现空会话的CSRF的另一种方法是添加:

skip_before_action :verify_authenticity_token

在您的Rails Controller中。这将确保您仍然可以访问会话信息。

同样,请确保仅在API控制器或CSRF保护不太适用的其他地方执行此操作。


1
这比protect_from_forgery except: [:my_method_name]什么相同?
阿诺德·罗阿

19

有关api.rubyonrails.org上API控制器的CSRF配置的相关信息:

重要的是要记住,XML或JSON请求也会受到影响,并且如果您要构建API,则应更改伪造保护方法ApplicationController(默认为:exception):

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

我们可能要禁用API的CSRF保护,因为它们通常设计为无状态。也就是说,请求API客户端将代替Rails为您处理会话。


4
这真令人困惑。我在说protect_from_forgeryAPI仍然必需的消息源和说不是必需的消息源之间徘徊。如果该API是用于使用会话cookie进行用户身份验证的单页应用程序,该怎么办?
威廉·贾德

CSRF机制是Rails内置的使用会话cookie处理攻击向量的方法。当与Rails会话cookie一起(实际上在内部)操作时,该机制可以保护您的控制器。如果没有protect_from_forgery,或者没有将其关闭或设置了except,您将告诉Rails不要使用CSRF令牌(来自会话cookie)中的信息来保护该操作。
詹森FB,

5
我认为令人困惑的是,对于Rails文档,“ API”表示服务器到服务器的应用程序,它将接收来自受信任的远程合作伙伴(并非访问您站点的所有Web用户)的API请求。对于那些人,请通过安全的不可破解机制提供唯一的密钥-秘密对。对于现代的Web应用程序来说,很多人将通过Web客户端加载Web应用程序,但您仍然使用基于会话或基于令牌的机制来唯一标识访问网站的每个人。因此,除非您使用某些其他机制(Json Web令牌等)来执行此操作,否则请坚持使用Rails的内置功能。
詹森FB,

1
你在AJAX做到这一点的方法是在这个SO岗位描述stackoverflow.com/questions/7203304/...
贾森FB

12

从Rails 5开始,您还可以使用:: API代替:: Base创建新类:

class ApiController < ActionController::API
end

3

如果要排除样本控制器的样本操作

class TestController < ApplicationController
  protect_from_forgery :except => [:sample]

  def sample
     render json: @hogehoge
  end
end

您可以毫无问题地处理来自外部的请求。


0

解决该问题的最简单方法是在控制器中执行标准操作,也可以将其直接放入ApplicationController中

class ApplicationController < ActionController::Base protect_from_forgery with: :exception, prepend: true end

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.