警告:无法验证CSRF令牌的真实性

时间:2013-01-09 03:42:51

标签: ruby-on-rails ajax cross-domain cors

我无法从shopify网站向我的rails应用程序发出跨域请求,该应用程序是作为shopify应用程序安装的。标题中所述的问题是我的服务器警告我Can't verify CSRF token authenticity我正在从我的rails应用程序返回的表单发出请求,其中包含相关的CSRF令牌。请求是使用jQuery的ajax方法完成的,预检OPTIONS请求由rack-cors处理。

我已按照this answer中的建议在我的标头中加入了X-CSRF-Token。我的帖子请求是从表单中提出的,因此我的问题没有得到解答here。确实正在处理选项请求(在this question中提到),正如我刚刚通过询问this question确认的那样。我已经坚持了一段时间,并做了一些阅读。

我将尝试通过代码片段遍历流程代码段,也许当我写完这篇文章时,我会发现问题的答案(如果发生这种情况,那么你就不会永远有机会阅读这一段。)

以下是我的控制器中的新方法和创建方法。

class AustraliaPostApiConnectionsController < ApplicationController

  # GET /australia_post_api_connections/new
  # GET /australia_post_api_connections/new.json
  def new
    # initializing variables

    respond_to do |format|
      puts "---------------About to format--------------------"
      format.html { render layout: false } # new.html.erb 
      format.json { render json: @australia_post_api_connection }
    end
  end

  # POST /australia_post_api_connections
  # POST /australia_post_api_connections.json
  def create

    @australia_post_api_connection = AustraliaPostApiConnection.new(params[:australia_post_api_connection])

    respond_to do |format|
      if @australia_post_api_connection.save

        format.js { render layout: false }
      else

        format.js { render layout: false }
      end
    end
  end
end

(我想知道create方法中的respond_to块,但我认为这不会导致CSRF令牌验证失败。)

在我的应用程序中,在/ AUSController / index,我有一个ajaxified GET请求,从/ AUSController / new返回表单。我的目标是能够在我的应用程序中从跨域起源进行所有相同的调用。现在GET请求适用于两者,因此我将忽略包含“新”形式。最终呈现HTML时,表单元素具有以下内容:

<form method="post" id="new_australia_post_api_connection" data-remote="true" class="new_australia_post_api_connection" action="http://localhost:3000/australia_post_api_connections" accept-charset="UTF-8">

<!-- a bunch more fields here -->

    <div class="field hidden">
      <input type="hidden" value="the_csrf_token" name="authenticity_token" id="tokentag">
    </div>
  </div>
</div>
</form>

CSRF令牌是通过调用form_authenticity_token生成的,详见参考资料mentioned above

在这两种情况下,下一步的做法有所不同:

我的应用程序根据ajax请求成功将新表单返回到商店。我在应用程序中对此进行了测试,即通过/ controller / index对/ controller / new进行ajax调用,然后提交表单。这就像一个魅力。在我的应用程序中成功发布的POST返回的js如下:

/ this is rendered when someone hits "calculate" and whenever the country select changes
:plain
  $("#shipping-prices").html("#{escape_javascript(render(:partial => 'calculations', :object => @australia_post_api_connection))}")

其中呈现以下部分,

= form_tag "/shipping_calculations", :method => "get" do

  = label_tag :shipping_type
  %br
  - @service_list.each_with_index do |service, index|
    - checked = true if index == 0
    = radio_button_tag(:shipping_type, service[:code], checked)
    = label_tag(:"shipping_type_#{service[:code]}", service[:name])
    = " -- $#{service[:price]}"
    %br

当我从同一个域调用它时,request.header包含以下内容:

HTTP_X_CSRF_TOKEN
the_token_I_expect=

rack.session
{
  "session_id"=>"db90f199f65554c70a6922d3bd2b7e61", 
  "return_to"=>"/", 
  "_csrf_token"=>"the_token_I_expect=", 
  "shopify"=>#<ShopifyAPI::Session:0x000000063083c8 @url="some-shop.myshopify.com", @token="some_token">
}

HTML很好地呈现和显示。

然而,从跨域来源来看,事情变得更加复杂。这是CORS和CSRF令牌和路由以及所有这些小细节开始蔓延的地方。特别是,当我进行ajax调用时,我使用以下脚本(它不存在于我的rails应用程序中,它存在于跨域服务器上)。这个ajax请求的动作由GET请求中的回调函数附加到提交按钮,为了完成,我已经包含了GET请求。

<script>

  var host = "http://localhost:3000/"
  var action = "australia_post_api_connections"

  console.log("start")
  $.ajax({
    url: host + action,
    type: "GET",
    data: { weight: 20 },
    crossDomain: true,
    xhrFields: {
      withCredentials: true
    },
    success: function(data) {
      console.log("success");
      $('#shipping-calculator').html(data);

      $('#new_australia_post_api_connection')
      .attr("action", host + action);

    $('.error').hide();

    $(".actions > input").click(function() {
      console.log("click")
      // validate and process form here
      $('.error').hide();

      var to_postcode = $("input#australia_post_api_connection_to_postcode").val();

      // client side validation
      if (to_postcode === "") {
        $("#postcode > .error").show();
        $("input#australia_post_api_connection_to_postcode").focus();
        return false;
      }

      tokentag = $('#tokentag').val()

      var dataHash = {
        to_postcode: to_postcode,
        authenticity_token: tokentag // included based on an SO answer
      }

      // included based on an SO answer
      $.ajaxSetup({
        beforeSend: function(xhr) {
          xhr.setRequestHeader('X-CSRF-TOKEN', tokentag);
        }
      });

      $.ajax({
        type: "POST",
        url: host + action,
        data: dataHash,
        success: function(data) {
          $('#shipping-prices').html(data);
        }
      }).fail(function() { console.log("fail") })
        .always(function() { console.log("always") })
        .complete(function() { console.log("complete") });
      return false;

    });

    }
  }).fail(function() { console.log("fail") })
  .always(function() { console.log("always") })
  .complete(function() { console.log("complete") });

  $(function() {
  });

</script>

但是,当我从这个远程位置(Shopify的远处斜坡)调用它时,我在请求标题中找到以下内容,

HTTP_X_CSRF_TOKEN
the_token_I_expect=

rack.session
{ }

我收到一个非常不愉快的NetworkError: 500 Internal Server Error而不是我想要的200 OK! ...在服务器端,我们发现日志抱怨说,

Started POST "/australia_post_api_connections" for 127.0.0.1 at 2013-01-08 19:20:25 -0800
Processing by AustraliaPostApiConnectionsController#create as */*
  Parameters: {"weight"=>"20", "to_postcode"=>"3000", "from_postcode"=>"3000", "country_code"=>"AUS", "height"=>"16", "width"=>"16", "length"=>"16", "authenticity_token"=>"the_token_I_expect="}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 6350ms

AustraliaPostApiConnection::InvalidError (["From postcode can't be blank", "The following errors were returned by the Australia Post API", "Please enter Country code.", "Length can't be blank", "Length is not a number", "Height can't be blank", "Height is not a number", "Width can't be blank", "Width is not a number", "Weight can't be blank", "Weight is not a number"]):
  app/models/australia_post_api_connection.rb:78:in `save'

缺乏rack.session似乎是可疑的,因为我的痛苦......但我找不到令人满意的答案。

最后,我认为适合包括我的机架式设置,以防它有用。

# configuration for allowing some servers to access the aus api connection
config.middleware.use Rack::Cors do
  allow do
    origins 'some-shop.myshopify.com'
    resource '/australia_post_api_connections',
      :headers => ['Origin', 'Accept', 'Content-Type', 'X-CSRF-Token'],
      :methods => [:get, :post]
  end
end

非常感谢您阅读所有这些内容。我希望答案与空rack.session有关。这至少会令人满意。

1 个答案:

答案 0 :(得分:2)

好吧,我的一位同事发现了这一点。问题是,我发送的内容与我在控制器中预期的哈希结构不同。

在我的控制器中,我实例化一个新的API连接,如下所示,

AustraliaPostApiConnection.new(params[:australia_post_api_connection])

我正在寻找params[:australia_post_api_connection],但我的数据哈希中没有这样的索引,看起来像,

var dataHash = {
  to_postcode: to_postcode,
  authenticity_token: tokentag // included based on an SO answer
}

为了解决这个问题,我将JS文件更改为包含,

var dataHash = {
  to_postcode: to_postcode,
}

var params = {
  australia_post_api_connection: dataHash,
  authenticity_token: tokentag // included based on an SO answer
}

现在它有效!谢谢同事!