我在从我的shopify网站到我的rails应用程序的跨域请求中遇到了麻烦,该应用程序是作为shopify应用程序安装的。问题如标题所述,我的服务器警告我,它是Can't verify CSRF token authenticity
,我正在从我的rails应用程序返回的表单发出请求,其中包括相关的CSRF令牌。这个请求是用jQuery的ajax方法完成的,而preflight OPTIONS请求是由rack-cors处理的。
我已经在我的标题中包含了这个答案中建议的X-CSRF-Token。我的帖子请求是通过表格发出的,所以我的问题在这里没有回答。选项请求(在这个问题中提到)确实正在处理中,正如我刚才通过问这个问题确认的那样。我被这个问题困扰了一段时间,并做了一些阅读。
我将尝试一个代码段一个代码段地遍历这个过程,也许在我写完这篇文章的时候,我就会发现我的问题的答案(如果发生这种情况,那么你就不会有机会读到这段话了)。
这里是我的控制器的new和create方法。
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
(我想知道创建方法中的respond_to块,但我不认为这会导致CSRF令牌验证失败。)
在我的应用程序中,在/AUSController/index,我有一个ajax 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
的调用生成的,详细信息在上面提到的一个参考文献中。
两种情况下的下一步操作是不同的:
我的应用程序成功地根据ajax请求将新表单返回给商店。我已经在应用程序中测试了这一点,即通过对/controller/new from/controller/index进行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
似乎是我痛苦的原因…但是我还没能找到一个满意的答案。
最后,我认为应该包括我的机架-cors设置,以防它有用。
# 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
有关。
我的一个同事发现了。问题是,我发送的has的结构与我在控制器中期望的哈希值不同。
在我的控制器中,我实例化了一个新的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
}
现在它工作了!谢谢同事!