Access control CORS
当资源请求来自不同域,协议或端口的资源时,资源会发出跨源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-face
CSS内的跨域字体使用),以便服务器可以部署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
- 除了由用户代理自动设置的标头(例如,
Connection
,User-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浏览器技术预览地方上的值的额外限制Accept
,Accept-Language
和Content-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
- 或者,如果,除了由用户代理自动设置的标头(例如,
Connection
,User-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浏览器技术预览地方上的值的额外限制Accept
,Accept-Language
和Content-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
,并说POST
,GET
和OPTIONS
是可行的方法来查询相关资源。请注意,此标头与Allow
响应标头相似,但在访问控制的上下文中严格使用。
服务器也发送Access-Control-Allow-Headers
一个值为“ X-PINGOTHER, Content-Type
”,确认这些是允许的头部与实际的请求一起使用。像Access-Control-Allow-Methods
,Access-Control-Allow-Headers
是可以接受的报头的逗号分隔的列表。
最后,Access-Control-Max-Age
给出以秒为单位的值,可以缓存对预检请求的响应多长时间,而不发送其他预检请求。在这种情况下,86400秒是24小时。请注意,每个浏览器都有一个最大内部值,当该Access-Control-Max-Age
值较大时优先。
预先发送的请求和重定向
大多数浏览器目前不支持预先发送的请求的重定向。如果针对预先发送的请求发生重定向,则大多数当前浏览器将报告如下的错误消息。
该请求被重定向到“ https://example.com/foo ”,该请求被禁止用于需要预检的跨请求请求。请求需要预检,不允许跟踪跨源重定向
CORS协议最初需要这种行为,但后来被更改为不再需要它。但是,大多数浏览器尚未实现此更改并仍显示最初所需的行为。
因此,除非浏览器赶上规范,否则您可以通过执行以下一项或两项操作来解决此限制:
- 更改服务器端行为以避免预检和/或避免重定向 - 如果您有控制服务器的请求正在进行
- 请更改请求,使其不会产生印前检查
但是如果不能做出这些改变,那么另一种可能的方式就是:
- 做一个简单的请求来确定(使用Response.url来获取API,或者使用XHR.responseURL来确定真正的预发光请求最终会到达哪个URL)。
- 使用从Response.url或XMLHttpRequest.responseURL获取的URL 在第一步中创建另一个请求(“真实”请求)。
但是,如果请求是由于请求中存在Authorization
标题而触发预检的请求,则无法使用上述步骤解决限制。除非您可以控制正在进行请求的服务器,否则您将无法彻底解决此问题。
请求凭证
由两个暴露的最有趣的功能XMLHttpRequest
或获取和CORS是使知道HTTP Cookie和HTTP认证信息的“持证”请求的能力。默认情况下,在跨站点XMLHttpRequest
或Fetch调用中,浏览器不会发送凭据。调用时,必须在XMLHttpRequest
对象或Request
构造函数上设置特定的标志。
在此示例中,最初加载的内容http://foo.example
向http://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-Header
和X-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
可以为空字符串; 例如,如果源是data
URL ,这很有用。
请注意,在任何访问控制请求中,始终发送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头部不能是通配符”