【问题标题】:rails - InvalidAuthenticityToken for json/xml requestsrails - 用于 json/xml 请求的 InvalidAuthenticityToken
【发布时间】:2010-11-17 18:04:20
【问题描述】:

由于某种原因,在使用 json 或 xml 向我的应用程序发出 post 请求时,我得到了 InvalidAuthenticityToken。我的理解是,rails 应该只对 html 或 js 请求需要一个真实性令牌,因此我不应该遇到这个错误。到目前为止,我发现的唯一解决方案是对我想通过 API 访问的任何操作禁用protect_from_forgery,但由于显而易见的原因,这并不理想。想法?

    def create
    respond_to do |format|
        format.html
        format.json{
            render :json => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
        }
        format.xml{
            render :xml => Object.create(:user => @current_user, :foo => params[:foo], :bar => params[:bar])
        }
    end
end

当我将请求传递给操作时,这就是我在日志中得到的内容:

 Processing FooController#create to json (for 127.0.0.1 at 2009-08-07 11:52:33) [POST]
 Parameters: {"foo"=>"1", "api_key"=>"44a895ca30e95a3206f961fcd56011d364dff78e", "bar"=>"202"}

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
  thin (1.2.2) lib/thin/connection.rb:76:in `pre_process'
  thin (1.2.2) lib/thin/connection.rb:74:in `catch'
  thin (1.2.2) lib/thin/connection.rb:74:in `pre_process'
  thin (1.2.2) lib/thin/connection.rb:57:in `process'
  thin (1.2.2) lib/thin/connection.rb:42:in `receive_data'
  eventmachine (0.12.8) lib/eventmachine.rb:242:in `run_machine'
  eventmachine (0.12.8) lib/eventmachine.rb:242:in `run'
  thin (1.2.2) lib/thin/backends/base.rb:57:in `start'
  thin (1.2.2) lib/thin/server.rb:156:in `start'
  thin (1.2.2) lib/thin/controllers/controller.rb:80:in `start'
  thin (1.2.2) lib/thin/runner.rb:174:in `send'
  thin (1.2.2) lib/thin/runner.rb:174:in `run_command'
  thin (1.2.2) lib/thin/runner.rb:140:in `run!'
  thin (1.2.2) bin/thin:6
  /opt/local/bin/thin:19:in `load'
  /opt/local/bin/thin:19

【问题讨论】:

    标签: ruby-on-rails json restful-authentication


    【解决方案1】:

    从 Rails 4.2 开始,我们有另一种方法是在 Rails 应用程序中使用 skip_before_filter 来避免 verify_authenticity_token:

    skip_before_action :verify_authenticity_token, only: [:action1, :action2]
    

    这会让 curl 完成它的工作。

    Ruby on Rails 4.2 发行说明:https://guiarails.com.br/4_2_release_notes.html

    【讨论】:

    • 这增加了一个安全问题。 verify_authenticity_token 运行是有原因的。
    【解决方案2】:

    只要 JavaScript 存在于 Rails 服务的网站上(例如:JS sn-p;或通过 webpacker 管理的 React 应用程序),您就可以默认使用 application.html.erb 中包含的 csrf_meta_tags 中的值。

    application.html.erb:

    <html>
      <head>
        ...
        <%= csrf_meta_tags %>
        ...
    

    因此在您网站的 HTML 中:

    <html>
      <head>
        <meta name="csrf-token" content="XZY">
    

    content 属性中获取令牌并在请求中使用它:

    const token = document.head.querySelector('meta[name="csrf-token"]').content
    
    const response = await fetch("/entities/1", {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ authenticity_token: token, entity: { name: "new name" } })
    });
    

    这类似于@andrewle 的回答,但不需要额外的令牌。

    【讨论】:

      【解决方案3】:

      这与@user1756254's answer 相同,但在 Rails 5 中您需要使用更多不同的语法:

      protect_from_forgery unless: -> { request.format.json? }
      

      来源:http://api.rubyonrails.org/v5.0/classes/ActionController/RequestForgeryProtection.html

      【讨论】:

      • 这不是安全风险吗?
      • @WylliamJudd 不,仅为 JSON 禁用它不会带来安全风险,因为防伪目标是保护从其他站点提交的 HTML 表单
      • 当官方文档说:'如果你正在构建一个API你应该改变ApplicationController中的伪造保护方法(默认情况下::exception)',那么它就足够安全了
      • 这看起来是个非常糟糕的主意,如果你的 cors 允许来自其他域的请求,或者攻击者在允许的域之一上有 js,那么攻击者可以发送 CSRF 请求。摘要:请不要这样做,您的请求应该传递自定义令牌来验证用户,或者从元标记中获取 csrf 令牌
      • 这里的文档有点粗心。 “我们可能希望禁用 API 的 CSRF 保护,因为它们通常设计为无状态。也就是说,请求 API 客户端将为您处理会话,而不是 Rails。”因此,如果您以其他方式对 API 请求进行身份验证,则可以禁用此功能,但不要这样做,例如,在您的应用中允许来自 JS sn-p 的经过身份验证的请求。查看我的回复。
      【解决方案4】:

      启用protect_from_forgery 后,Rails 需要一个真实性令牌来处理任何非 GET 请求。 Rails 会自动将真实性令牌包含在使用表单助手创建的表单或使用 AJAX 助手创建的链接中——因此在正常情况下,您不必考虑它。

      如果您没有使用内置的 Rails 表单或 AJAX 助手(也许您正在做不引人注目的 JS 或使用 JS MVC 框架),您必须自己在客户端设置令牌并发送它在提交 POST 请求时连同您的数据一起。您可以在布局的&lt;head&gt; 中添加这样的一行:

      <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
      

      然后您的 AJAX 函数会将令牌与您的其他数据一起发布(以 jQuery 为例):

      $.post(url, {
          id: theId,
          authenticity_token: window._token
      });
      

      【讨论】:

      • Optimate 应该接受这个答案。顺便说一句,如果您使用$.ajax,则需要将真实性令牌放在数据字段中,如下所示:$.ajax( { data: { authenticity_token: window._token } } )
      【解决方案5】:

      要补充费尔南多的答案,如果您的控制器同时响应 json 和 html,您可以使用:

            skip_before_filter :verify_authenticity_token, if: :json_request?
      

      【讨论】:

      • 这不是安全风险吗?
      • 是的。不要这样做。
      【解决方案6】:

      添加到 andymism 的答案,您可以使用它在每个 POST 请求中应用默认包含 TOKEN:

      $(document).ajaxSend(function(event, request, settings) {
          if ( settings.type == 'POST' ||  settings.type == 'post') {
              settings.data = (settings.data ? settings.data + "&" : "")
                  + "authenticity_token=" + encodeURIComponent( window._token );
          }
      });
      

      【讨论】:

        【解决方案7】:

        我遇到了类似的情况,问题是我没有通过正确的内容类型标头发送 - 我请求的是text/json,而我应该请求的是application/json

        我使用curl 测试我的应用程序(根据需要进行修改):

        curl -H "Content-Type: application/json" -d '{"person": {"last_name": "Lambie","first_name": "Matthew"}}' -X POST http://localhost:3000/people.json -i
        

        或者您可以将 JSON 保存到本地文件并像这样调用curl

        curl -H "Content-Type: application/json" -v -d @person.json -X POST http://localhost:3000/people.json -i
        

        当我将内容类型标题更改为右侧 application/json 时,我所有的麻烦都消失了,我不再需要禁用伪造保护。

        【讨论】:

        • 这对 Web 应用程序实际上并不适用。浏览器无法运行 curl。 =(
        • 重点是我用 curl 调试了问题,发现发送正确的 Content-Type 标头很重要。
        猜你喜欢
        • 2020-02-22
        • 1970-01-01
        • 1970-01-01
        • 2018-11-02
        • 1970-01-01
        • 2014-01-13
        • 2014-02-10
        • 2021-12-20
        • 1970-01-01
        相关资源
        最近更新 更多