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

Posted

技术标签:

【中文标题】Rails:发出 POST 请求时无法验证 CSRF 令牌的真实性【英文标题】:Rails: Can't verify CSRF token authenticity when making a POST request 【发布时间】:2016-05-12 21:40:42 【问题描述】:

我想将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 时应该是我的错误。

我还需要做其他设置吗?

【问题讨论】:

对于 API,通常接受关闭 CSRF 令牌验证。我用protect_from_forgery with: :null_session 【参考方案1】:

跨站请求伪造 (CSRF/XSRF) 是指恶意网页诱使用户执行非预期的请求,例如通过使用小书签、iframe 或仅通过创建视觉上相似到足以欺骗用户的页面。

Rails CSRF protection 是为“经典”网络应用程序设计的 - 它只是提供一定程度的保证,即请求来自您自己的网络应用程序。 CSRF 令牌就像一个只有你的服务器知道的秘密——Rails 生成一个随机令牌并将其存储在会话中。您的表单通过隐藏的输入发送令牌,Rails 会验证任何非 GET 请求是否包含与会话中存储的内容匹配的令牌。

然而,API 通常被定义为跨站点,并且不仅仅用于您的网络应用程序,这意味着 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 中间件和许多其他多余的组件。

http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf https://labs.kollegorna.se/blog/2015/04/build-an-api-now/ WARNING: Can't verify CSRF token authenticity rails

【讨论】:

谢谢,我选择关闭部分 CSRF:***.com/questions/5669322/… 这表明所有 API 都服务于应用程序到应用程序的流量(其中向单个合作伙伴颁发唯一的密钥和秘密)。在那种情况下,就像服务器到服务器的通信一样,这个答案是合适的。然而,让大多数 Web 应用程序开发人员感到困惑的是,对于由您控制和编写的 javascript 客户端,您不想使用单个密钥(这会将单个密钥公开给所有客户端)。相反,Rail 的 CSRF 和 cookie 会话机制工作得很好——即使对于使用你的 API 的 Javascript 应用程序——如果你在每个请求中将 CSRF 令牌传递回 Rails。 在此 SO 帖子 ***.com/questions/7203304/… 中描述了您在 AJAX 中执行此操作的方式 @JasonFB 你是对的,因为javascript客户端的单一秘密不起作用。但是,如果您打算构建一个也可供其他类型的非浏览器客户端使用的 API,那么使用会话和 Rails CSRF 保护仍然存在问题。 如果您提供或构建 CSRF 的替代方案,请成为我的客人。只需知道要替换的原因,就知道用什么来替换它是合适的。显然,现在我们的狡辩变成了上下文。【参考方案2】:

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

skip_before_action :verify_authenticity_token

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

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

【讨论】:

这和protect_from_forgery except: [:my_method_name]一样吗?【参考方案3】:

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。

【讨论】:

这太令人困惑了。我在说protect_from_forgery 对于 API 仍然是必要的消息来源和说它不是的消息来源之间摇摆不定。如果 API 用于单页应用程序,使用会话 cookie 进行用户身份验证怎么办? CSRF 机制是 Rails 使用会话 cookie 处理攻击向量的内置方法。该机制保护您的控制器,同时在 Rails 会话 cookie 旁边(实际上是在内部)运行。如果没有protect_from_forgery,或者将其关闭或设置一个except,你就是在告诉Rails 不要使用来自CSRF 令牌(取自会话cookie)的信息来保护该操作。 我认为令人困惑的是,对于 Rails 文档,“API”是指一个服务器到服务器的应用程序,它将接收来自受信任的远程合作伙伴(不是所有访问您网站的 Web 用户)的 API 请求。对于这些人,请通过安全的不可破解机制提供唯一的密钥-秘密对。对于现代 Web 应用程序,许多人将通过 Web 客户端加载您的 Web 应用程序,您仍然使用基于会话或令牌的机制来唯一标识访问您网站的每个人。因此,除非您使用其他机制来执行此操作(Json Web 令牌等),否则请坚持使用 Rails 的内置内容。 在此 SO 帖子 ***.com/questions/7203304/… 中描述了您在 AJAX 中执行此操作的方式【参考方案4】:

从 Rails 5 开始,您还可以使用 ::API 而不是 ::Base: 创建一个新类:

class ApiController < ActionController::API
end

【讨论】:

【参考方案5】:

如果您使用的是 Devise,请注意

对于 Rails 5,protect_from_forgery 不再附加到 before_action 链中,因此如果您在 protect_from_forgery 之前设置了 authenticate_user,您的请求将导致“无法验证 CSRF 令牌真实性”。要解决此问题,请更改您调用它们的顺序,或使用protect_from_forgery prepend: true

Documentation

【讨论】:

【参考方案6】:

如果您只想为一个或多个控制器操作(而不是整个控制器)跳过 CSRF 保护,试试这个

skip_before_action :verify_authenticity_token, only [:webhook, :index, :create]

[:webhook, :index, :create] 将跳过对这 3 个操作的检查,但您可以更改为任何您想跳过的操作

【讨论】:

【参考方案7】:

如果要排除示例控制器的示例操作

class TestController < ApplicationController
  protect_from_forgery except: :sample

  def sample
   render json: @hogehoge
  end
end

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

【讨论】:

【参考方案8】:

在 Rails 6 中,我找到了解决“无法验证 CSRF 令牌真实性”的简单方法。只是放

config.action_controller.default_protect_from_forgery = false # unless ENV["RAILS_ENV"] == "production"

在 application.rb 中

它在开发模式下很方便。但不要在生产中使用它。

【讨论】:

【参考方案9】:

这个问题最简单的解决方法是在你的控制器中做标准的事情,或者你可以直接把它放到ApplicationController

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

【讨论】:

以上是关于Rails:发出 POST 请求时无法验证 CSRF 令牌的真实性的主要内容,如果未能解决你的问题,请参考以下文章

Rails API 422 无法处理的实体:没有可用的验证密钥,heroku

向 Fastify 发出 POST 请求时,JSON 未被解析以进行验证

当请求来自用户登录的页面时,自动验证DRF。如果外部发出api请求,则请求令牌验证

无法使用赛普拉斯发送 POST 登录请求

当连接是移动数据 xamarin 表单时,无法在服务器上发出 Put 和 Post 请求

使用 Javascript 发出 HTTP POST 身份验证基本请求