访问控制-允许-源标头如何工作?非简单请求

2022-08-29 22:01:30

显然,我完全误解了它的语义。我想到了这样的东西:

  1. 客户端从源下载 javascript 代码 MyCode.js。http://siteA
  2. MyCode.js的响应标头包含 Access-Control-Allow-Origin: http://siteB,我认为这意味着 MyCode.js 被允许对站点 B 进行跨源引用。
  3. 客户端触发 MyCode.js 的某些功能,这些功能反过来又向 发出请求,尽管是跨域请求,但应该没问题。http://siteB

好吧,我错了。它根本不像这样工作。因此,我已经阅读了跨域资源共享,并尝试在w3c推荐中阅读跨域资源共享

有一件事是肯定的 - 我仍然不明白我应该如何使用这个标题。

我完全控制站点 A 和站点 B。如何使从站点 A 下载的 javascript 代码能够使用此标头访问站点 B 上的资源?

附言

我不想使用JSONP。


答案 1

Access-Control-Allow-Origin一个 CORS(跨源资源共享)标头

当站点 A 尝试从站点 B 获取内容时,站点 B 可以发送响应标头,以告知浏览器此页面的内容可供某些来源访问。(是一个域,加上一个方案和端口号。默认情况下,站点B的页面不能被任何其他来源访问;使用标头为特定请求源的跨域访问打开了一扇门。Access-Control-Allow-OriginAccess-Control-Allow-Origin

对于站点 B 希望站点 A 访问的每个资源/页面,站点 B 应使用响应标头为其页面提供服务:

Access-Control-Allow-Origin: http://siteA.com

现代浏览器不会完全阻止跨域请求。如果站点 A 从站点 B 请求页面,则浏览器实际上将在网络级别获取请求的页面,并检查响应标头是否将站点 A 列为允许的请求者域。如果站点 B 未指示允许站点 A 访问此页面,浏览器将触发 的事件并拒绝对请求 JavaScript 代码的响应数据。XMLHttpRequesterror

非简单请求

在网络级别上发生的事情可能比上面解释的稍微复杂一。如果请求是“非简单”请求,浏览器首先发送无数据的“预检”OPTIONS 请求,以验证服务器是否接受该请求。当以下一项(或两者)时,请求不是简单的:

  • 使用GET或POST以外的HTTP动词(例如PUT,DELETE)
  • 使用非简单的请求标头;唯一的简单请求标头是:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(仅当其值为 、 或application/x-www-form-urlencodedmultipart/form-datatext/plain)

如果服务器使用与非简单谓词和/或非简单谓词匹配的适当响应标头(对于非简单标头,对于非简单谓词)响应 OPTIONS 预检,则浏览器将发送实际请求。Access-Control-Allow-HeadersAccess-Control-Allow-Methods

假设站点 A 想要发送一个 PUT 请求,其非简单值为 ,浏览器将首先发送一个预检请求:/somePageContent-Typeapplication/json

OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

请注意,并由浏览器自动添加;您不需要添加它们。此 OPTIONS 印前检查获取成功的响应标头:Access-Control-Request-MethodAccess-Control-Request-Headers

Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type

发送实际请求时(在预检完成后),行为与处理简单请求的方式相同。换句话说,预检成功的非简单请求与简单请求的处理方式相同(即,服务器仍必须再次发送以获得实际响应)。Access-Control-Allow-Origin

浏览器发送实际请求:

PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json

{ "myRequestContent": "JSON is so great" }

服务器会发回 一个 ,就像它发出一个简单的请求一样:Access-Control-Allow-Origin

Access-Control-Allow-Origin: http://siteA.com

有关非简单请求的详细信息,请参阅了解 XMLHttpRequest over CORS


答案 2

跨域资源共享 - (又名跨域AJAX请求)是大多数Web开发人员可能遇到的问题,根据同源策略,浏览器限制客户端JavaScript在安全沙箱中,通常JS无法直接与来自不同域的远程服务器通信。过去,开发人员创造了许多棘手的方法来实现跨域资源请求,最常用的方法是:CORS

  1. 使用 Flash/Silverlight 或服务器端作为“代理”与远程通信。
  2. JSON with padding (JSONP).
  3. 将远程服务器嵌入 iframe 并通过片段或 window.name 进行通信,请参阅此处

这些棘手的方法或多或少都有一些问题,例如,如果开发人员只是简单地“评估”它,JSONP可能会导致安全漏洞,而上面的#3,尽管它有效,但两个域都应该在彼此之间建立严格的合同,它既不灵活也不优雅恕我直言:)

W3C引入了跨域资源共享(CORS)作为标准解决方案,以提供安全,灵活和推荐的标准方法来解决此问题。

机制

从高层次上讲,我们可以简单地认为CORS是来自域A的客户端AJAX调用与域B上托管的页面之间的契约,典型的跨域请求/响应将是:

域 A AJAX 请求标头

Host DomainB.com
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us;
Accept-Encoding gzip, deflate
Keep-Alive 115
Origin http://DomainA.com 

域 B 响应标头

Cache-Control private
Content-Type application/json; charset=utf-8
Access-Control-Allow-Origin DomainA.com
Content-Length 87
Proxy-Connection Keep-Alive
Connection Keep-Alive

我上面标记的蓝色部分是核心事实,“Origin”请求标头“指示跨域请求或预检请求的来源”,“访问控制-允许-源”响应标头指示此页面允许来自 DomainA 的远程请求(如果值为 * 表示允许来自任何域的远程请求)。

正如我上面提到的,W3建议浏览器在提交实际的跨域HTTP请求之前实现“预检请求”,简而言之,它是一个HTTP请求:OPTIONS

OPTIONS DomainB.com/foo.aspx HTTP/1.1

如果 foo.aspx 支持 OPTIONS HTTP 谓词,它可能会返回如下响应:

HTTP/1.1 200 OK
Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainA.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Type: application/json

仅当响应包含“访问控制-允许-源”并且其值为“*”或包含提交 CORS 请求的域时,通过满足此强制条件,浏览器才会提交实际的跨域请求,并将结果缓存在“印前检查-结果-缓存”中。

三年前,我在博客上写了关于CORS的博客:AJAX跨源HTTP请求