什么是跨域资源共享(CORS)
跨域资源共享是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了自己以外的其他 origin
(域、协议和端口),浏览器可以访问加载这些资源。
跨域资源共享还通过一种机制来检查服务器是否允许要发送真实请求,该机制通过浏览器发送一个到服务器托管的跨域源资源的 预检
请求。预检
中浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
在 http://domain-a.com 页面中对 https://domain-b.com 发送一个请求
跨域资源共享标准新增一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,对于那些可能对服务器数据产生副作用的 HTTP 方法( GET
以外的 HTTP 请求,或者搭配某些 MIME类型 的 POST
请求),浏览器必须首先使用 OPTIONS
方法发起一个预检请求(preflight request),获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。
场景
简单请求
不会触发 CORS 预检请求
。若请求满足以下条件,则视为“简单请求”:
- 使用
GET
、HEAD
、POST
。 - 除了类用户代理自动设置的首部字段(Connection、User-Agent)和在
Fetch
规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为Fetch
规范定义的对CORS
安全的首部字段集合:- Accept
- Accept-Language
- Content-Language
- Content-Type
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencodedvar
- 请求中的任意
XMLHttpRequest
对象均没有注册任何事件监听器;XMLHttpRequest
对象可以使用XMLHttpRequest.upload
属性访问 - 请求中没有使用
ReadableStream
对象。
测试一个简单请求:
- 客户端
http://test-a.test
:1
2
3
4var xhr = new XMLHttpRequest();
var url = 'http://test-b.test/test-cors';
xhr.open('post', url);
xhr.send(); - 服务端
http://test-b.test
:响应头增加:Access-Control-Allow-Origin: *
对于简单请求,只需在服务器响应中添加 Access-Control-Allow-Origin: *
,可以被任意外域访问。
预检请求 (浏览器自动发送)
用于检查服务器是否支持 CORS 即跨域资源共享,使用 OPTIONS
方法发起一个预检请求到服务器。
一般请求头部会包含: Access-Control-Allow-Method
、Access-Control-Allow-Headers
、Origin
测试
- 客户端:
1
2
3
4
5var xhr = new XMLHttpRequest();
var url = 'http://test-b.test/test-cors';
xhr.open('post', url);
xhr.setRequestHeader('Content-Type', 'application/xml'); // 增加 首部字段
xhr.send();
如果现在不修改服务端代码,会怎么样?
控制台报错:Access to XMLHttpRequest at ‘http://test-b.test/test-cors' from origin ‘http://test-a.test' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
预检请求的响应 Access-Control-Allow-Headers 中不允许请求头部有 Content-type 字段
服务端:
需再在响应头部增加
Access-Control-Allow-Headers: Content-type
额外:
Access-Control-Allow-Methods
:服务器允许客户端使用什么方法发起请求Access-Control-Max-Age
:响应的有效时间内浏览器不需要为同一请求再发送预检请求。
注意:
预检请求的成功仅限于 200~299 状态,其他状态会导致不会被共享或使 CORS 预检请求失败
附带身份凭证的请求
当发出跨源请求时,第三方 cookie 策略仍将适用
测试:
- 客户端:
1
2
3
4
5var xhr = new XMLHttpRequest();
var url = 'http://test-b.test/test-cors';
xhr.open('post', url);
xhr.withCredentials = true; // 发送凭证信息
xhr.send();
不修改服务端的情况下发送请求尝试
控制台报错:The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
意思大体就是当请求附带凭证时响应中
Access-Control-Allow-Origin
的值不能是*
,而且还告诉说XMLHttpRequest
发起带身份凭据的请求是withCredentials
属性控制的。
- 服务端:
响应头部修改Access-Control-Allow-Origin: http://test-a.test
,并且还需增加Access-Control-Allow-Credentials: true
。
注意:
在响应附带身份凭证的请求时:服务器不能将
Access-Control-Allow-Origin
、Access-Control-Allow-Headers
、Access-Control-Allow-Methods
的值设置为*
。
总结
HTTP 响应头部字段
- Access-Control-Allow-Origin:允许访问该资源的外域 URI,如果值为具体域名,则在响应首部中
Vary
字段值必须包含Origin
- Access-Control-Allow-Headers:请求所允许使用的 HTTP 方法
- Access-Control-Allow-Methods:求中允许携带的首部字段
- Access-Control-Max-Age:缓存预检请求结果
- Access-Control-Allow-Credentials:决定请求是否可以使用 credentials
- Access-Control-Expose-Headers:控制
XMLHttpRequest
对象的getResponseHeader()
方法获得除基本响应头(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)以外的响应头。
参见:跨域资源共享 、 https://fetch.spec.whatwg.org/#http-cors-protocol