为什么我的 CORS 配置没有导致服务器过滤传入请求?如何让服务器只接受来自特定来源的请求?
Why isn't my CORS configuration causing the server to filter incoming requests? How can I make the server only accept requests from a specific origin?
我希望我的 Rails 5 API-only 应用程序,目前 运行 http://localhost:3000
,只接受来自我的 NodeJS 前端应用程序的请求,目前 运行 http://localhost:8888
。
所以我这样配置 /config/initializers/cors.rb
:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "http://localhost:8888"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
我写了这个测试:
#/spec/request/cors_request_spec.rb
RSpec.feature "CORS protection", type: :request do
it "should accept a request from a whitelisted domain" do
get "/api/v1/bodies.json", nil, "HTTP_ORIGIN": "http://localhost:8888"
expect(response.status).to eql(200)
end
it "should reject a request from a non-whitelisted domain" do
get "/api/v1/bodies.json", nil, "HTTP_ORIGIN": "https://foreign.domain"
expect(response.status).to eql(406)
end
end
第一个测试按预期通过。但是第二个失败,响应代码为 200。为什么?
(顺便说一下,我不喜欢 406 响应代码;只是一个表示请求不会被满足的代码。)
CORS 配置不会阻止服务器根据 Origin
请求 header 的值接受请求。你不能仅仅通过 CORS 配置来做到这一点。
当您在服务器上配置 CORS 支持时,服务器所做的所有不同只是发送 Access-Control-Allow-Origin
响应 header 和其他 CORS 响应 header。
CORS 限制的实际执行仅由浏览器完成。它不是由服务器强制执行的。
CORS 的工作方式是:无论您在服务器端进行的任何 CORS 配置如何,服务器都会继续接受来自所有客户端和源的请求,否则它会继续接收请求,因此来自所有源的所有客户端都会继续从服务器获得响应就像他们本来会的那样。
因此,即使您在浏览器开发工具中看到来自前端 JavaScript 代码的 cross-origin 请求失败的错误,您仍然可以在浏览器开发工具中看到响应。
但是仅仅因为您的浏览器可以看到响应并不意味着浏览器会将其暴露给您的前端代码。如果服务器将请求发送到 opts-in 以允许通过响应 Access-Control-Allow-Origin
header 可以确定起源。
因此,对于任何具有 Origin
请求 header 匹配 https://foreign.domain
的请求,问题中的配置片段应该导致浏览器在客户端发出消息说 http://localhost:3000/api/v1/bodies.json
无法加载,因为响应中没有 Access-Control-Allow-Origin
响应 header(因为您的配置导致服务器仅发送 header 作为对您列入白名单的来源的响应)。
但这就是您通过 CORS 所能做的全部。您不能仅通过在服务器端进行任何 CORS 配置来阻止服务器端接受和响应来自特定来源的请求。如果你想这样做,你需要使用除 CORS 之外的其他东西。
我希望我的 Rails 5 API-only 应用程序,目前 运行 http://localhost:3000
,只接受来自我的 NodeJS 前端应用程序的请求,目前 运行 http://localhost:8888
。
所以我这样配置 /config/initializers/cors.rb
:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "http://localhost:8888"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
我写了这个测试:
#/spec/request/cors_request_spec.rb
RSpec.feature "CORS protection", type: :request do
it "should accept a request from a whitelisted domain" do
get "/api/v1/bodies.json", nil, "HTTP_ORIGIN": "http://localhost:8888"
expect(response.status).to eql(200)
end
it "should reject a request from a non-whitelisted domain" do
get "/api/v1/bodies.json", nil, "HTTP_ORIGIN": "https://foreign.domain"
expect(response.status).to eql(406)
end
end
第一个测试按预期通过。但是第二个失败,响应代码为 200。为什么?
(顺便说一下,我不喜欢 406 响应代码;只是一个表示请求不会被满足的代码。)
CORS 配置不会阻止服务器根据 Origin
请求 header 的值接受请求。你不能仅仅通过 CORS 配置来做到这一点。
当您在服务器上配置 CORS 支持时,服务器所做的所有不同只是发送 Access-Control-Allow-Origin
响应 header 和其他 CORS 响应 header。
CORS 限制的实际执行仅由浏览器完成。它不是由服务器强制执行的。
CORS 的工作方式是:无论您在服务器端进行的任何 CORS 配置如何,服务器都会继续接受来自所有客户端和源的请求,否则它会继续接收请求,因此来自所有源的所有客户端都会继续从服务器获得响应就像他们本来会的那样。
因此,即使您在浏览器开发工具中看到来自前端 JavaScript 代码的 cross-origin 请求失败的错误,您仍然可以在浏览器开发工具中看到响应。
但是仅仅因为您的浏览器可以看到响应并不意味着浏览器会将其暴露给您的前端代码。如果服务器将请求发送到 opts-in 以允许通过响应 Access-Control-Allow-Origin
header 可以确定起源。
因此,对于任何具有 Origin
请求 header 匹配 https://foreign.domain
的请求,问题中的配置片段应该导致浏览器在客户端发出消息说 http://localhost:3000/api/v1/bodies.json
无法加载,因为响应中没有 Access-Control-Allow-Origin
响应 header(因为您的配置导致服务器仅发送 header 作为对您列入白名单的来源的响应)。
但这就是您通过 CORS 所能做的全部。您不能仅通过在服务器端进行任何 CORS 配置来阻止服务器端接受和响应来自特定来源的请求。如果你想这样做,你需要使用除 CORS 之外的其他东西。