Access control CORS

优质
小牛编辑
131浏览
2023-12-01

当资源请求来自不同域,协议或端口的资源时,资源会发出跨源HTTP请求。例如,http://domain-a.com<img> src提供的HTML页面提出了http://domain-b.com/image.jpg的请求。今天网络上的许多页面都会加载资源,如来自不同域的CSS样式表,图像和脚本。

出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。例如,XMLHttpRequest并且取遵循同源策略。因此,使用XMLHttpRequest或提取 的Web应用程序只能向自己的域发出HTTP请求。为了改进Web应用程序,开发人员要求浏览器供应商允许跨域请求。

跨源资源共享(CORS)机制为Web服务器提供跨域访问控制,从而实现安全的跨域数据传输。现代浏览器在API容器中使用CORS(例如XMLHttpRequest或Fetch)来缓解跨源HTTP请求的风险。

本文适用于Web管理员,服务器开发人员和前端开发人员。现代浏览器处理跨源共享的客户端组件,包括标头和策略实施。但是这个新标准意味着服务器必须处理新的请求和响应头文件。另一篇关于服务器开发人员从服务器角度讨论跨源共享的文章(使用PHP代码片段)是补充阅读。

这种跨源共享标准用于为以下项目启用跨站点HTTP请求:

  • 如上所述,以跨站点的方式调用XMLHttpRequest或提取 API。
  • Web字体(用于@font-faceCSS内的跨域字体使用),以便服务器可以部署TrueType字体,这些字体只能跨站点加载并由允许这样做的网站使用。
  • WebGL纹理。
  • 使用绘制到画布的图像/视频帧drawImage
  • 样式表(用于CSSOM访问)。
  • 脚本(用于非静音例外)。

本文是跨源资源共享的一般性讨论,并包括对必要HTTP标头的讨论。

总览

跨源资源共享标准的工作原理是添加新的HTTP标头,允许服务器描述允许使用Web浏览器读取该信息的一组原点。此外,对于可能对服务器数据产生副作用的HTTP请求方法(特别是针对除特定MIME类型之外的HTTP方法GET或针对POST某些MIME类型使用的HTTP方法),规范要求浏览器“预检”请求,请求来自服务器与HTTP OPTIONS请求方法,然后,在从服务器获得“批准”后,使用实际的HTTP请求方法发送实际请求。服务器还可以通知客户端“凭证”(包括Cookie和HTTP认证数据)是否应该随请求一起发送。

后续章节讨论方案,并提供所使用的HTTP标头的细目。

访问控制场景的示例

在这里,我们提供了三个场景来说明跨源资源共享如何工作。所有这些示例都使用该XMLHttpRequest对象,该对象可用于在任何支持的浏览器中进行跨站点调用。

这些部分包含的JavaScript代码片段(以及正确处理这些跨站点请求的服务器代码的运行实例)可以在http://arunranga.com/examples/access-control/中找到“实际操作” ,并且将在支持跨站点的浏览器中工作XMLHttpRequest

从服务器的角度讨论跨源资源共享(包括PHP代码片段)可以在服务器端访问控制(CORS)文章中找到。

简单的请求

有些请求不会触发CORS预检。这些在本文中被称为“简单请求”,尽管Fetch规范(它定义了CORS)不使用该术语。不会触发CORS预检的请求(所谓的“简单请求”)是满足以下所有条件的请求:

  • 唯一允许的方法是:
    • GET
    • HEAD
    • POST
  • 除了由用户代理自动设置的标头(例如,ConnectionUser-Agent,或任何与所述抓取规格为“禁止的标题名称”定义名称其它标题的),其允许被手动设置仅标头是那些Fetch规范将其定义为“CORS安全列表请求标头”,它们是:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (但请注意下面的附加要求)
    • Last-Event-ID
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type标题的唯一允许值是:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 没有事件侦听器XMLHttpRequestUpload在请求中使用的任何对象上注册。
  • ReadableStream请求中没有使用对象。

注意:这些是与Web内容已经发布的相同类型的跨站点请求,并且除非服务器发送适当的标头,否则不会向请求者发布响应数据。因此,防止跨站点请求伪造的站点对HTTP访问控制毫无新意。

注:允许在WebKit每日和Safari浏览器技术预览地方上的值的额外限制AcceptAccept-LanguageContent-Language头。如果任何这些头文件具有“非标准”值,则WebKit / Safari不会将该请求视为符合“简单请求”的条件。除了以下WebKit错误之外,WebKit / Safari认为这些标头的“非标准”值没有记录:对非标准CORS安全列表的请求标头需要预检Accept,Accept-Language和Content-Language,允许逗号,简单CORS的Accept-Language和Content-Language请求标题,以及在简单的CORS请求中切换到黑名单模型以获取受限制的Accept标头。没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。

例如,假设域上的网页内容http://foo.example希望调用域上的内容http://bar.other。这种代码可以在部署在foo.example上的JavaScript中使用:

var invocation = new XMLHttpRequest();var url = 'http://bar.other/resources/public-data/';   function callOtherDomain() {  if(invocation) {    
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }}

这将导致客户端和服务器之间的简单交换,使用CORS头来处理权限:

让我们看看在这种情况下浏览器将发送到服务器的内容,然后让我们看看服务器如何响应:

GET /resources/public-data/ HTTP/1.1Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

1 - 10行发送标题。这里需要注意的主要HTTP请求头是Origin上面第10行的头,表示调用来自域上的内容http://foo.example

第13 - 22行显示了域上服务器的HTTP响应http://bar.other。作为响应,服务器发回一个Access-Control-Allow-Origin头部,如上面第16行所示。使用Origin头部和Access-Control-Allow-Origin显示访问控制协议的最简单的用法。在这种情况下,服务器回应一个Access-Control-Allow-Origin: *,这意味着资源可以通过任何域以跨站点方式访问。如果资源所有者http://bar.other希望将资源的访问限制为仅来自请求的资源http://foo.example,则他们将发回:

Access-Control-Allow-Origin: http://foo.example

请注意,现在除了http://foo.example(由请求中的ORIGIN:标头标识的,如上面的第10行)之外,没有任何域可以以跨站点方式访问资源。该Access-Control-Allow-Origin头应包含在请求的发送的值Origin头。

预先请求的请求

与上面讨论的“简单请求”不同,“preflighted”请求首先通过OPTIONS方法向另一个域上的资源发送HTTP请求,以确定实际请求是否安全发送。跨站请求是这样预检的,因为它们可能会影响用户数据。

特别是,如果满足以下任一条件,则会请求一个请求:

  • 如果请求使用以下任何一种方法:
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 或者,如果,除了由用户代理自动设置的标头(例如,ConnectionUser-Agent,或任何与所述抓取规格为“禁止的标题名称”中定义的名称其它头的),该请求包括比其他任何头那些Fetch规范将其定义为“CORS安全列表请求标头”,它们是:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (但请注意下面的附加要求)
    • Last-Event-ID
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 或者,如果该Content-Type头具有比其他如下的值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 或者,如果XMLHttpRequestUpload在请求中使用的对象上注册了一个或多个事件侦听器。
  • 或者如果ReadableStream请求中使用了对象。

注:允许在WebKit每日和Safari浏览器技术预览地方上的值的额外限制AcceptAccept-LanguageContent-Language头。如果这些标头中的任何一个具有“非标准”值,WebKit / Safari会预检请求。除了以下WebKit错误之外,WebKit / Safari认为这些标头的“非标准”值没有记录:对非标准CORS安全列表的请求标头需要预检Accept,Accept-Language和Content-Language,允许逗号,简单CORS的Accept-Language和Content-Language请求标题,以及在简单的CORS请求中切换到黑名单模型以获取受限制的Accept标头。没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。

以下是一个将被预冲的请求示例。

var invocation = new XMLHttpRequest();var url = 'http://bar.other/resources/post-here/';var body = '<?xml version="1.0"?><person><name>Arun</name></person>';    function callOtherDomain(){  if(invocation)    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }}......

在上面的示例中,第3行创建了一个XML主体,以便POST在第8行中发送请求。另外,在第9行中,设置了一个“自定义”(非标准)HTTP请求标头X-PINGOTHER: pingpong。这样的头文件不是HTTP / 1.1协议的一部分,但通常对Web应用程序有用。由于请求使用Content-Type application/xml,并且由于设置了自定义标头,所以该请求是预检的。

(注意:如下所述,实际的POST请求不包含Access-Control-Request- *标题;它们仅用于OPTIONS请求。)

我们来看看客户端和服务器之间的完整交换。第一个交换是预检请求/响应

OPTIONS /resources/post-here/ HTTP/1.1Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

预检请求完成后,发送真正的请求:

POST /resources/post-here/ HTTP/1.1Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

上面的第1-12行代表使用该OPTIONS方法的预检请求。浏览器根据上述JavaScript代码片段使用的请求参数确定需要发送该消息,以便服务器可以响应以实际请求参数发送请求是否可接受。OPTIONS是一个HTTP / 1.1方法,用于确定来自服务器的更多信息,并且是一种安全的方法,这意味着它不能用于更改资源。请注意,除OPTIONS请求外,还会发送另外两个请求标头(分别为第10行和第11行):

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method报头通知服务器作为预检请求被发送的实际请求时,它将被与发送的一部分POST请求方法。该Access-Control-Request-Headers头通知服务器发送实际的请求时,它将被发送的X-PINGOTHER和Content-Type自定义页眉。服务器现在有机会确定它是否希望在这种情况下接受请求。

上面的第14-26行是服务器发回的响应,表明请求方法(POST)和请求头(X-PINGOTHER)是可接受的。具体来说,我们来看第17-20行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

服务器响应Access-Control-Allow-Methods,并说POSTGETOPTIONS是可行的方法来查询相关资源。请注意,此标头与Allow响应标头相似,但在访问控制的上下文中严格使用。

服务器也发送Access-Control-Allow-Headers一个值为“ X-PINGOTHER, Content-Type”,确认这些是允许的头部与实际的请求一起使用。像Access-Control-Allow-MethodsAccess-Control-Allow-Headers是可以接受的报头的逗号分隔的列表。

最后,Access-Control-Max-Age给出以秒为单位的值,可以缓存对预检请求的响应多长时间,而不发送其他预检请求。在这种情况下,86400秒是24小时。请注意,每个浏览器都有一个最大内部值,当该Access-Control-Max-Age值较大时优先。

预先发送的请求和重定向

大多数浏览器目前不支持预先发送的请求的重定向。如果针对预先发送的请求发生重定向,则大多数当前浏览器将报告如下的错误消息。

该请求被重定向到“ https://example.com/foo ”,该请求被禁止用于需要预检的跨请求请求。请求需要预检,不允许跟踪跨源重定向

CORS协议最初需要这种行为,但后来被更改为不再需要它。但是,大多数浏览器尚未实现此更改并仍显示最初所需的行为。

因此,除非浏览器赶上规范,否则您可以通过执行以下一项或两项操作来解决此限制:

  • 更改服务器端行为以避免预检和/或避免重定向 - 如果您有控制服务器的请求正在进行
  • 请更改请求,使其不会产生印前检查

但是如果不能做出这些改变,那么另一种可能的方式就是:

  1. 做一个简单的请求来确定(使用Response.url来获取API,或者使用XHR.responseURL来确定真正的预发光请求最终会到达哪个URL)。
  2. 使用从Response.url或XMLHttpRequest.responseURL获取的URL 在第一步中创建另一个请求(“真实”请求)。

但是,如果请求是由于请求中存在Authorization标题而触发预检的请求,则无法使用上述步骤解决限制。除非您可以控制正在进行请求的服务器,否则您将无法彻底解决此问题。

请求凭证

由两个暴露的最有趣的功能XMLHttpRequest或获取和CORS是使知道HTTP Cookie和HTTP认证信息的“持证”请求的能力。默认情况下,在跨站点XMLHttpRequest或Fetch调用中,浏览器不会发送凭据。调用时,必须在XMLHttpRequest对象或Request构造函数上设置特定的标志。

在此示例中,最初加载的内容http://foo.examplehttp://bar.other设置了Cookie 的资源发出简单的GET请求。foo.example上的内容可能包含JavaScript,如下所示:

var invocation = new XMLHttpRequest();var url = 'http://bar.other/resources/credentialed-content/';    function callOtherDomain(){  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }}

第7行显示了XMLHttpRequest必须设置的标志,以便使用Cookie进行调用,即withCredentials布尔值。默认情况下,调用不使用Cookies。由于这是一个简单的GET请求,这不是预检,但浏览器将拒绝不具有任何的响应Access-Control-Allow-Credentials: true报头,并没有提供给调用web内容的响应。

以下是客户端和服务器之间的示例交换:

GET /resources/access-control-with-credentials/ HTTP/1.1Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

虽然第11行包含http://bar.other指向内容的Cookie ,但如果bar.other没有响应Access-Control-Allow-Credentials: true(第19行),则响应将被忽略并且不会提供给Web内容。

认证请求和通配符

在响应有证书请求时,服务器必须Access-Control-Allow-Origin标题的值中指定一个源,而不是指定“ *”通配符。

由于上述示例中的请求标头包含Cookie标头,因此如果Access-Control-Allow-Origin标头的值为“*” ,则请求将失败。但它不会失败:因为Access-Control-Allow-Origin头部的值是“ http://foo.example”(实际起源)而不是“ *”通配符,所以证书认知内容将返回到调用的Web内容。

请注意,上述Set-Cookie示例中的响应标头还设置了另一个cookie。如果发生故障,则会引发异常(取决于所使用的API)。

HTTP响应标头

本部分列出服务器为“跨源资源共享”规范定义的访问控制请求发送的HTTP响应头。上一节将概述这些实际情况。

访问控制允许来源

返回的资源可能有一个Access-Control-Allow-Origin标题,其语法如下:

Access-Control-Allow-Origin: <origin> | *

origin参数指定可以访问资源的URI。浏览器必须执行此操作。对于没有凭据的请求,服务器可以指定“*”作为通配符,从而允许任何来源访问资源。

例如,要允许http://mozilla.org访问资源,您可以指定:

Access-Control-Allow-Origin: http://mozilla.org

如果服务器指定的是原始主机而不是“*”,那么它也可以在Vary响应头中包含Origin来向客户机指出服务器响应将根据Origin请求头的值而不同。

访问控制展示报头

Access-Control-Expose-Headers标题即可让所有浏览器都允许访问的服务器白名单头。例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这允许X-My-Custom-HeaderX-Another-Custom-Header标题暴露给浏览器。

访问控制 - 最大 - 年龄

Access-Control-Max-Age报头指示预检请求的结果多久可以被缓存。有关预检请求的示例,请参阅上述示例。

Access-Control-Max-Age: <delta-seconds>

delta-seconds参数表示可以缓存结果的秒数。

访问控制允许的凭据

Access-Control-Allow-Credentials报头指示是否对所述请求的响应可以在被暴露credentials标记为真。当作为对预检请求的响应的一部分使用时,这指示是否可以使用凭证进行实际请求。请注意,简单的GET请求不是预检的,所以如果请求使用凭证的资源,如果此资源不与资源一起返回,浏览器将忽略该响应,并且不会返回到Web内容。

Access-Control-Allow-Credentials: true

以上讨论了认证请求。

访问控制允许的方法

Access-Control-Allow-Methods头指定访问资源时所允许的一种或多种方法。这用于响应预检请求。上面讨论了预先请求的条件。

Access-Control-Allow-Methods: <method>[, <method>]*

上面给出了一个预检请求的示例,其中包括将此标头发送给浏览器的示例。

访问控制允许报头

所述Access-Control-Allow-Headers报头在响应用来预检请求,以指示在进行实际请求时HTTP标头都可以使用。

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

HTTP请求标头

本节列出客户端在发出HTTP请求时可能使用的标题,以便利用跨源共享功能。请注意,在调用服务器时会为您设置这些标头。使用跨站点XMLHttpRequest功能的开发人员不必以编程方式设置任何跨源共享请求标头。

起源

Origin报头指示跨站点接入请求或预检请求的来源。

Origin: <origin>

起源是一个URI,表示请求发起的服务器。它不包含任何路径信息,但仅包含服务器名称。

注:origin可以为空字符串; 例如,如果源是dataURL ,这很有用。

请注意,在任何访问控制请求中,始终发送Origin标题。

访问控制请求法

Access-Control-Request-Method发出的预检要求,让服务器知道实际的请求时会怎样使用HTTP方法时使用。

Access-Control-Request-Method: <method>

这种用法的例子可以在上面找到。

访问控制请求报头

Access-Control-Request-Headers发出的预检要求,让服务器知道什么实际的请求时HTTP标头的时候会用到头使用。

Access-Control-Request-Headers: <field-name>[, <field-name>]*

这种用法的例子可以在上面找到。

产品规格

规范

状态

评论

在该规范中获取'CORS'的定义。

生活水平

新定义; 取代W3C CORS规范。

浏览器兼容性

特征

Chrome

Edge

火狐

Internet Explorer

Opera

苹果浏览器

基本支持

4

12

3.5

10

12

4

特征

Android的

适用于Android的Chrome

Edge 移动

适用于Android的Firefox

IE手机

Opera Android

iOS Safari

基本支持

2.1

(是)

(是)

1.0

(是)

12

3.2

兼容性说明

  • Internet Explorer 8和9通过该XDomainRequest对象公开CORS ,但在IE 10中完全实现。
  • 尽管Firefox 3.5引入了对跨站点XMLHttpRequests和Web字体的支持,但某些请求在新版本之前是有限的。具体来说,Firefox 7引入了WebGL纹理的跨站点HTTP请求功能,并且Firefox 9添加了对使用的画布上绘制的图像的支持drawImage

扩展内容

  • 代码示例显示XMLHttpRequest和跨源资源共享
  • 跨服务器端透视资源共享(PHP等)
  • 跨源资源共享规范
  • XMLHttpRequest
  • 取API
  • 与所有(现代)浏览器一起使用CORS
  • 使用CORS - HTML5岩石
  • 堆栈溢出回答与“如何”信息处理常见问题:
    • 如何避免CORS预检
    • 如何使用CORS代理来解决“无访问控制 - 允许源标头”
    • 如何解决“Access-Control-Allow-Origin头部不能是通配符”