CORS
跨域资源共享(CORS)
- 跨域资源共享 (CORS) 是W3C标准
- 跨域 (协议、域名/ip、端口有一个不一样的两个url就是跨域) 查看同源策略
- 共享指的是:域名A的网页请求域名B的资源
解决了什么
- 现代浏览器都有同源限制,域名A不允许访问域名B的资源
跨域请求的一个示例:
https://domain-a.com
用XMLHttpRequest
访问https://domain-b.com/data.json
出于安全原因,浏览器会限制从脚本发起的跨域 HTTP 请求。
CORS 机制支持浏览器和服务器之间的安全跨域请求和数据传输。现代浏览器在 APIXMLHttpRequest
或Fetch 中使用 CORS来降低跨域 HTTP 请求的风险。
哪些请求使用 CORS?
-
XMLHttpRequest
或Fetch API - Web 字体用于
@font-face
CSS 中的跨域字体使用 - WebGL 纹理。
-
drawImage()
. - CSS 图像形状。
谁限制跨域,如何绕过限制
- 举例域名http://www.a.com 需要访问http://www.b.com/c.json,由于浏览器同源限制,无法访问到。
- 得出:
- 限制是谁控制的——浏览器。如果我们用非浏览器环境(如后端java代码),请求c.json,没有浏览器限制,是可以访问到的。
- 很多情况下我们希望浏览器不要限制,域名www.b.com希望浏览器允许www.a.com访问他的资源,可以在请求www.b.com的资源的时候,相应的header上设置Access-Control-Allow-Origin,告诉浏览器,我允许哪些域名访问我的这个资源,如果设置成*,则所有域名都可以访问。
- 简单说限制是浏览器限制的,但是解除限制要服务端处理
- 还有别的方法吗?
- jsonp!
- 缺点是只支持get,优点是支持老式浏览器,但是现在基本不考虑这些老掉牙的ie了
- 大概原理是:a网页请求b服务器的script并且传入请求成功后应该调用哪个回调函数,返回的script里面有需要的资源数据,并且调用回调函数传入数据。
- 还有没有方法,chrome 启动的时候传入关闭命令参数
"C:\Users\UserName\AppData\Local\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir
简单请求和非简单请求(预检请求)
- 浏览器在处理这两种请求,方法不太一样,详解比较复杂,有兴趣可以继续看看
简单的请求
简单请求不会触发CORS 预检。这些称为simple requests,尽管Fetch规范(定义 CORS)不使用该术语。简单请求是满足以下所有条件的请求:
- 允许的方法:
- 除了由用户代理自动设置的标头(例如,
Connection
,User-Agent
或Fetch 规范中定义为禁止标头名称的其他标头)外,唯一允许手动设置的标头是Fetch 规范定义的标头一个 CORS-safelisted request-header,它们是:Accept
Accept-Language
Content-Language
-
Content-Type
(请注意以下附加要求)
- 允许的
Content-Type
:application/x-www-form-urlencoded
multipart/form-data
text/plain
- 如果使用
XMLHttpRequest
对象发出请求,则不会在请求中使用的XMLHttpRequest.upload
属性返回的对象上注册事件侦听器;也就是说,给定一个XMLHttpRequest
实例xhr
,没有代码调用xhr.upload.addEventListener()
添加事件侦听器来监视上传。 -
ReadableStream
请求中没有使用任何对象。
注: WebKit每日和Safari浏览器技术预览放置在允许的值的额外限制Accept
,Accept-Language
和Content-Language
头。如果这些标头中的任何一个具有“非标准”值,则 WebKit/Safari 不会将该请求视为“简单请求”。没有记录 WebKit/Safari 认为“非标准”的值,除了以下 WebKit 错误:
- 要求对非标准 CORS 安全列表请求标头进行预检 Accept、Accept-Language 和 Content-Language
- 允许在 Accept、Accept-Language 和 Content-Language 请求标头中使用逗号以用于简单 CORS
- 在简单的 CORS 请求中切换到受限制的 Accept 标头的黑名单模型
没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。
例如,假设 web 内容https://foo.example
希望调用域上的内容https://bar.other
。此类代码可能用于部署在foo.example
以下位置的JavaScript :
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
此操作在客户端和服务器之间执行简单的交换,使用 CORS 标头来处理权限:
我们来看看这种情况下浏览器会向服务器发送什么,看看服务器是如何响应的:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Origin: https://foo.example
note 的请求标头是Origin
,这表明调用来自https://foo.example
。
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
作为响应,服务器返回一个Access-Control-Allow-Origin
带有的标头Access-Control-Allow-Origin: *
,这意味着该资源可以被任何来源访问。
Access-Control-Allow-Origin: *
Origin
和Access-Control-Allow-Origin
头的这种模式是访问控制协议的最简单用法。如果资源所有者https://bar.other
希望将对该资源的访问限制为仅来自 的请求https://foo.example
(即,除了https://foo.example
可以跨站点方式访问该资源之外的任何域),他们将发送:
Access-Control-Allow-Origin: https://foo.example
注意:响应凭据请求请求时,服务器必须在Access-Control-Allow-Origin
标头值中指定来源,而不是指定“ *
”通配符。
预检请求
与简单请求不同,对于“预检”请求,浏览器首先使用该OPTIONS
方法向另一个源上的资源发送 HTTP 请求,以确定实际请求是否可以安全发送。此类跨站点请求是预检的,因为它们可能会对用户数据产生影响。
以下是将被预检的请求示例:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
上面的示例创建了一个 XML 正文以与POST
请求一起发送。此外,还设置了一个非标准的 HTTPX-PINGOTHER
请求标头。此类标头不是 HTTP/1.1 的一部分,但通常对 Web 应用程序有用。由于请求使用Content-Type
of application/xml
,并且设置了自定义标头,因此该请求是预检的。
注意:如下所述,实际的POST
请求不包含Access-Control-Request-*
头部;它们仅用于OPTIONS
请求。
让我们看看客户端和服务器之间的完整交换。第一个交换是预检请求/响应:
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://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
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
上面的第 1 - 10 行表示该OPTIONS
方法的预检请求。浏览器根据上面的 JavaScript 代码片段使用的请求参数确定它需要发送此请求,以便服务器可以响应是否可以使用实际请求参数发送请求。OPTIONS 是一种 HTTP/1.1 方法,用于确定来自服务器的更多信息,并且是一种安全的方法,这意味着它不能用于更改资源。请注意,与 OPTIONS 请求一起发送了另外两个请求标头(分别为第 9 行和第 10 行):
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
自定义页眉。现在服务器有机会确定在这些条件下它是否可以接受请求。
上面的第 13 - 22 行是服务器返回的响应,表明请求方法 ( POST
) 和请求头 ( X-PINGOTHER
) 是可以接受的。让我们仔细看看第 16-19 行:
Access-Control-Allow-Origin: https://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-Origin: https://foo.example
,仅限制对请求源域的访问。它还响应Access-Control-Allow-Methods
,它说,POST
和GET
有效的方法来查询资源问题(这个头是类似的Allow
响应头,但访问控制的范围内严格使用)。
服务器还发送Access-Control-Allow-Headers
值“ X-PINGOTHER, Content-Type
”,确认这些是允许用于实际请求的标头。像Access-Control-Allow-Methods
,Access-Control-Allow-Headers
是以逗号分隔的可接受标头列表。
最后,Access-Control-Max-Age
给出在不发送另一个预检请求的情况下可以缓存对预检请求的响应多长时间的值(以秒为单位)。默认值为 5 秒。在本例中,最大年龄为 86400 秒(= 24 小时)。请注意,每个浏览器都有一个最大内部值,当Access-Control-Max-Age
超过它时优先。
预检请求完成后,发送真正的请求:
POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://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 XML payload]
预检请求和重定向
并非所有浏览器当前都支持在预检请求后进行重定向。如果在这样的请求之后发生重定向,目前一些浏览器会报告如下错误信息:
请求被重定向到“https://example.com/foo”,这对于需要预检的跨域请求是不允许的。请求需要预检,不允许遵循跨源重定向。
CORS 协议最初要求该行为,但随后更改为不再需要它。但是,并非所有浏览器都实现了更改,因此仍然表现出最初要求的行为。
在浏览器赶上规范之前,您可以通过执行以下一项或两项操作来解决此限制:
- 更改服务器端行为以避免预检和/或避免重定向
- 更改请求,使其成为不会导致预检的简单请求
如果这是不可能的,那么另一种方法是:
- 发出一个简单的请求(
Response.url
用于 Fetch API,或XMLHttpRequest.responseURL
)来确定真正的预检请求最终会到达哪个 URL。 - 使用您从第一步或在第一步中获得的 URL 发出另一个请求(真正的请求)。
Response.url``XMLHttpRequest.responseURL
但是,如果请求是由于请求中存在Authorization
标头而触发预检的请求,则您将无法使用上述步骤解决此限制。除非您可以控制向其发出请求的服务器,否则您根本无法解决它。
带有凭据的请求
注意:当向不同域发出凭据请求时,第三方 cookie 策略仍将适用。无论本章中描述的服务器和客户端上的任何设置如何,该策略都会始终强制执行。
由两个暴露的最有趣的功能XMLHttpRequest
或获取和CORS是让“特命”要求是知道的能力的HTTP cookies和HTTP验证信息。默认情况下,在跨站点XMLHttpRequest
或Fetch调用中,浏览器不会发送凭据。调用时必须在XMLHttpRequest
对象或Request
构造函数上设置特定标志。
在此示例中,最初加载的内容https://foo.example
向https://bar.other
设置 Cookie的资源发出简单的 GET 请求。foo.example 上的内容可能包含这样的 JavaScript:
const invocation = new XMLHttpRequest();
const url = 'https://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
布尔值。默认情况下,调用是在没有 Cookie 的情况下进行的。由于这是一个简单的GET
请求,它没有预检,但浏览器将拒绝任何没有标头的响应,并且不会使响应可用于调用 Web 内容。Access-Control-Allow-Credentials
: true
这是客户端和服务器之间的示例交换:
GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://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]
尽管第 10 行包含发往 上的内容的 Cookie https://bar.other
,但如果 bar.other 没有以(第 17 行)响应,则响应将被忽略并且无法用于 Web 内容。Access-Control-Allow-Credentials
: true
预检请求和凭据
CORS 预检请求不得包含凭据。对预检请求的响应必须指定Access-Control-Allow-Credentials: true
指示可以使用凭据进行实际请求。
注意:某些企业身份验证服务要求在预检请求中发送 TLS 客户端证书,这违反了Fetch规范。
Firefox 87 允许通过将首选项设置network.cors_preflight.allow_client_cert
为true
(错误 1511151)来启用此不合规行为。当前,基于 Chromium 的浏览器始终在 CORS 预检请求中发送 TLS 客户端证书(Chrome 错误 775438)。
有凭据的请求和通配符
响应认证请求时:
- 服务器不得
*
为Access-Control-Allow-Origin
响应头值指定“ ”通配符,而必须指定明确的来源;例如:Access-Control-Allow-Origin: https://example.com
- 服务器不得
*
为Access-Control-Allow-Headers
响应标头值指定“ ”通配符,而必须指定一个明确的标头名称列表;例如,Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- 服务器不得
*
为Access-Control-Allow-Methods
响应头值指定“ ”通配符,而必须指定一个明确的方法名称列表;例如,Access-Control-Allow-Methods: POST, GET
如果请求包含凭据(最常见的是Cookie
标头)并且响应包含Access-Control-Allow-Origin: *
标头(即带有通配符),浏览器将阻止对响应的访问,并在 devtools 控制台中报告 CORS 错误。
但是,如果请求确实包含凭据(如Cookie
标头)并且响应包含实际来源而不是通配符(例如Access-Control-Allow-Origin: https://example.com
),则浏览器将允许访问来自指定来源的响应。
另请注意,如果Set-Cookie
响应中的Access-Control-Allow-Origin
值是“ *
” 通配符而不是实际来源,则响应中的任何响应标头都不会设置 cookie 。
第三方 cookie
请注意,在 CORS 响应中设置的 cookie 受常规第三方 cookie 政策的约束。在上面的示例中,页面是从 加载的,foo.example
但第 20 行的 cookie 是由 发送的bar.other
,因此如果用户的浏览器配置为拒绝所有第三方 cookie,则不会保存。
请求中的 cookie(第 10 行)也可能在正常的第三方 cookie 策略中被抑制。因此,强制执行的 cookie 策略可能会使本章中描述的功能无效,从而有效地阻止您发出任何有凭据的请求。
将应用围绕SameSite属性的Cookie 政策。
HTTP 响应标头
本节列出了服务器为跨域资源共享规范定义的访问控制请求返回的 HTTP 响应标头。上一节概述了这些操作。
访问控制允许来源
返回的资源可能有一个Access-Control-Allow-Origin
具有以下语法的标头:
Access-Control-Allow-Origin: <origin> | *
Access-Control-Allow-Origin
指定一个单一的来源,它告诉浏览器允许该来源访问资源;否则——对于没有凭据的请求——“ *
”通配符告诉浏览器允许任何来源访问资源。
例如,要允许来自源的代码https://mozilla.org
访问资源,您可以指定:
Vary: Origin
如果服务器指定单个来源(可能会根据请求来源作为许可名单的一部分动态更改)而不是“ *
”通配符,则服务器还应Origin
在Vary
响应标头中包含以向客户端指示服务器响应将基于在Origin
请求头的值上。
访问控制公开标题
的Access-Control-Expose-Headers
报头将指定的标头的JavaScript(如允许列表getResponseHeader()
)在浏览器中被允许访问。
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
例如,以下内容:
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-Request-Headers
标头的响应。
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
HTTP 请求标头
本节列出了客户端在发出 HTTP 请求以利用跨域共享功能时可能使用的标头。请注意,这些标头是在调用服务器时为您设置的。使用跨站点XMLHttpRequest
功能的开发人员不必以编程方式设置任何跨源共享请求标头。
起源
的Origin
报头指示跨站点接入请求或预检请求的来源。
Origin: <origin>
源是一个 URL,指示发起请求的服务器。它不包含任何路径信息,仅包含服务器名称。
注意:该origin
值可以是null
。
请注意,在任何访问控制请求中,始终发送Origin
标头。
访问控制请求方法
该Access-Control-Request-Method
发行预检请求,让服务器知道实际的请求时什么HTTP方法将被使用时使用。
Access-Control-Request-Method: <method>
访问控制请求头
该Access-Control-Request-Headers
发行预检请求,让服务器知道当实际请求时(如用什么HTTP头的时候会用到头被使用setRequestHeader()
)。此浏览器端标头将由 的补充服务器端标头回答Access-Control-Allow-Headers
。
Access-Control-Request-Headers: <field-name>[, <field-name>]*