了解简单 HTTP 和 XHR 请求之间的区别。
Origin Web 应用程序 - https://github.com
当您通过输入 Origin Web App 打开网站时,您的浏览器会发出简单的 HTTP 请求。 CORS 在这里不适用。因为我们自愿请求提供来自 Origin Web App 的内容。您的浏览器将/应该毫无问题地提供它。但是,当涉及 XHR 请求时,浏览器中 Origin Web App 加载的 JavaScript 可以向任何请求资源的服务器(https://netflix.com)发出请求。现在,Origin Web App 归 Github 所有,但 https://netflix.com 归 Netflix 所有,该服务器可能可以提供任何服务。 Github 无法控制 Netflix 拥有的服务器。这会带来很多安全隐患,可能会将内容从一个网站(可能是金融网站)窃取到任何远程服务器。
幸运的是,CORS 通过一组给定的规则很好地解决了这个问题。
它是 W3C 定义的标准,允许客户端(浏览器)和服务器之间的跨域请求共享资源,同时保持安全性。任何浏览器都将遵守这些标准,以防止从任何第三方服务器加载资源。
标头指示请求的来源。它与 CORS 请求以及 POST 请求一起发送。
句法:
Origin: <scheme> "://" <hostname> [":" <port> ]
示例:
Origin: https://netflix.com
Origin: http://netflix.com:443
Origin: http://localhost:1443
浏览器在发出预检请求时使用此标头来指示发出实际请求时将使用哪种请求方法。
句法:
Access-Control-Request-Method: <method>
<method>
可以是GET
、 POST
或DELETE
。
例子:
Access-Control-Request-Method: POST
该标头再次在浏览器的飞行前请求中使用,以指示发出实际请求时要使用哪些请求标头。要使用多个标头,必须用逗号分隔。
句法:
Access-Control-Request-Headers: <header-name>
Access-Control-Request-Headers: <header-name>, <header-name>
例子:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
重要提示:所有标头都应该是 CORS 安全列表或自定义标头(例如X-Custom-Header
。
Accept
Accept-Language
Content-Language
Content-Type
允许的值为application/x-www-form-urlencoded
、 multipart/form-data
和text/plain
。
以下标头将在预检请求中返回。
该标头指示请求的 Origin 是否被允许。您的浏览器将通过将请求的来源与其匹配来选择请求成功/失败。
句法:
Access-Control-Allow-Origin: *
对于没有凭据的请求,可以将值*
指定为通配符。这告诉您的浏览器允许来自任何来源的请求。
Access-Control-Allow-Origin: <origin>
当您在响应标头中仅收到一个来源时,这意味着您的服务器/网络应用程序基于请求的Origin
如果允许,它会使用相同的Origin
进行响应。您的服务器还应该响应 Vary,以表明它根据请求标头而变化。
例子:
Access-Control-Allow-Origin: https://github.com
如果此标头中的值之一匹配,您的浏览器将发出实际请求。当返回通配符*
时,表示允许任何方法。
句法:
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
例子:
Access-Control-Allow-Methods: GET, POST
如果允许所有请求的标头,您的浏览器将发出实际请求。
句法:
Access-Control-Allow-Headers: <header-name>, <header-name>
Access-Control-Allow-Headers: *
例子:
Access-Control-Allow-Headers: Accept, Content-Type
通配符*
告诉浏览器允许任何标头。
告诉飞行前的结果可以缓存多长时间。
Access-Control-Max-Age: <delta-seconds>
最大数量可以缓存结果的秒数。
每个浏览器都有最大限制,
Vary header 通常用于缓存目的,以确定是否可以使用缓存的响应,或者是否必须根据 header 值重新缓存它。
在 CORS 中,假设服务器根据请求的Origin
允许多个源,它将在Access-Control-Allow-Origin
中返回特定的 URL。
句法:
Vary: <header-name>
例子:
假设 github.com 允许 https://github.com 和 https://netflix.com 请求资源。考虑以下场景,
场景一:
卷曲 -X 选项 https://github.com/api/v1/gists/1
Origin: https://github.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://github.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
现在在这种情况下,浏览器将缓存此飞行前请求结果 10 分钟。
场景2:
卷曲 -X 选项 https://github.com/api/v1/gists/1
Origin: https://netflix.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://netflix.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
现在您可能会注意到Access-Control-Allow-Origin
包含 https://netflix.com,这是响应如何根据给定Origin
变化的示例。与第一种情况不同,此响应的 max-age 也仅缓存 5 分钟。
某些请求不会触发 CORS 的飞行前请求。我们将这些称为简单请求。应满足以下条件:
允许的方法之一:
除了用户代理(例如Connection
或User-Agent
)自动设置的标头之外,唯一允许手动设置的标头是 CORS 安全列表请求标头和以下内容:
DPR
Downlink
Save-Data
Viewport-Width
Width
请求中使用的任何XMLHttpRequestUpload
对象上都没有注册任何事件侦听器。
请求中未使用ReadableStream
对象。
例子:
要求:
GET /api/v1/public-data/ HTTP/1.1
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
响应标头:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
当请求在Access-Control-Allow-Origin
标头中返回*
时。这意味着可以从任何Host
发出请求。在这种情况下,您的浏览器将不会发出飞行前请求。
您的浏览器将发出飞行前请求,以确定实际请求是否可以安全发送。
例子:
const xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , 'https://bar.com/api/v1/post-here/' ) ;
xhr . setRequestHeader ( 'X-PINGOTHER' , 'pingpong' ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json;charset=UTF-8' ) ;
xhr . onreadystatechange = handler ;
xhr . send ( JSON . stringify ( { "email" : "[email protected]" } ) ) ;
飞行前要求:
OPTIONS /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept
实际要求:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
当设置像X-PINGOTHER
这样的非标准标头时,您的浏览器将不知道发出请求是否安全。为了确保其安全,您的浏览器会使用包含X-PINGOTHER
和Content-Type
Access-Control-Request-Headers
发出 OPTIONS 请求。在验证飞行前请求的响应标头后,您的浏览器会发出实际请求。
一般来说,当您发出 XHR 请求时,cookie 不会随请求一起传递。当需要传递 cookie 时,您必须在XMLHttpRequest
对象上设置一个标志。
const xhr = new XMLHttpRequest ( ) ;
const url = 'http://bar.com/api/v1/credentialed-content/' ;
function callOtherDomain ( ) {
if ( invocation ) {
xhr . open ( 'GET' , url , true ) ;
xhr . withCredentials = true ;
xhr . onreadystatechange = handler ;
xhr . send ( ) ;
}
}
要求:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
Cookie: _session=NyV14oKXiS6HHopaf9nT
当发出XMLHttpRequest
请求并设置了withCredentials
标志时,浏览器将在请求标头中传递Cookie
。
回复:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: _session=AjBSqxj4T7bSySNTWeEm; expires=Wed, 31-05-2020 00:00:00 GMT
当您的浏览器注意到Access-Control-Allow-Credentials
设置为 true 时。它将尊重Set-Cookie
标头并设置 cookie。
重要提示:不应在Access-Control-Allow-Origin
中设置“*”通配符,如“凭据请求和通配符”部分中提到的那样。
当通过设置withCredentials
标志发出凭据请求时,服务器必须设置Access-Control-Expose-Headers
以让浏览器知道可以访问哪些标头。
在 Cors 出现之前的世界中,默认情况下,浏览器无法在 CORS 请求中访问响应标头。因此它是显式的,以便浏览器将查找此标头以读取公开的标头。通过这种方式,CORS 规范可以确保旧浏览器不会崩溃。
这是在飞行前请求中返回的。当您的浏览器看到此信息时,它可以访问Set-Cookie
标头。正如我们上面提到的,在正常的 XHR 请求中,浏览器不会在请求标头中传递Cookie
,也不会读取Set-Cookie
响应标头。
句法:
Access-Control-Allow-Credentials: true
您可以在“带有凭据的请求”部分找到示例。
当我们说凭证请求时,它意味着在 XHR 请求中传递的 cookie 或通过响应标头Set-Cookie
设置的 cookie。当使用 withCredentials 标志发出 XHR 请求时,您希望收到响应以及要设置的 cookie。但是,您不能期望Access-Control-Allow-Origin
为“*”,因为这意味着任何网站都可以使用这些 cookie。因此,如果您的浏览器在Access-Control-Allow-Origin
响应标头中看到“*”,则请求将失败。
当预检请求响应 301/302 时,某些浏览器当前可能不支持此功能。您可能会收到如下错误:
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
注意:有关解决方法,请检查 Mozilla 的预检请求和重定向文档。
当用户启用时,浏览器具有拒绝所有third-party
cookie 的设置。例如,如果请求来自https://foo.com
并且服务器位于https://bar.com
,则您的浏览器将不会设置https://bar.com
发送的 cookie。