CORS 所有知识点完全解析

CORS

跨域资源共享CORS

  • 跨域资源共享 (CORS) 是W3C标准
  • 跨域 (协议、域名/ip、端口有一个不一样的两个url就是跨域) 查看同源策略
  • 共享指的是:域名A的网页请求域名B的资源
image.png

解决了什么

  • 现代浏览器都有同源限制,域名A不允许访问域名B的资源

跨域请求的一个示例:
https://domain-a.comXMLHttpRequest访问https://domain-b.com/data.json

出于安全原因,浏览器会限制从脚本发起的跨域 HTTP 请求。

image.png

CORS 机制支持浏览器和服务器之间的安全跨域请求和数据传输。现代浏览器在 APIXMLHttpRequestFetch 中使用 CORS来降低跨域 HTTP 请求的风险。

哪些请求使用 CORS?

谁限制跨域,如何绕过限制

  • 举例域名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)不使用该术语。简单请求满足以下所有条件的请求

注: WebKit每日和Safari浏览器技术预览放置在允许的值的额外限制AcceptAccept-LanguageContent-Language头。如果这些标头中的任何一个具有“非标准”值,则 WebKit/Safari 不会将该请求视为“简单请求”。没有记录 WebKit/Safari 认为“非标准”的值,除了以下 WebKit 错误:

没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。

例如,假设 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 标头来处理权限:

image.png

我们来看看这种情况下浏览器会向服务器发送什么,看看服务器是如何响应的:

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: *

OriginAccess-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-Typeof application/xml,并且设置了自定义标头,因此该请求是预检的。

image.png

注意:如下所述,实际的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-PINGOTHERContent-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,它说,POSTGET有效的方法来查询资源问题(这个头是类似的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 协议最初要求该行为,但随后更改为不再需要它。但是,并非所有浏览器都实现了更改,因此仍然表现出最初要求的行为。

在浏览器赶上规范之前,您可以通过执行以下一项或两项操作来解决此限制:

  • 更改服务器端行为以避免预检和/或避免重定向
  • 更改请求,使其成为不会导致预检的简单请求

如果这是不可能的,那么另一种方法是:

  1. 发出一个简单的请求Response.url用于 Fetch API,或XMLHttpRequest.responseURL)来确定真正的预检请求最终会到达哪个 URL。
  2. 使用您从第一步或在第一步中获得的 URL 发出另一个请求(真正的请求)。Response.url``XMLHttpRequest.responseURL

但是,如果请求是由于请求中存在Authorization标头而触发预检的请求,则您将无法使用上述步骤解决此限制。除非您可以控制向其发出请求的服务器,否则您根本无法解决它。

带有凭据的请求

注意:当向不同域发出凭据请求时,第三方 cookie 策略仍将适用。无论本章中描述的服务器和客户端上的任何设置如何,该策略都会始终强制执行。

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

在此示例中,最初加载的内容https://foo.examplehttps://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

image.png

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

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_certtrue错误 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

如果服务器指定单个来源(可能会根据请求来源作为许可名单的一部分动态更改)而不是“ *”通配符,则服务器还应OriginVary响应标头中包含以向客户端指示服务器响应将基于在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-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-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>]*
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容