跨域资源共享(CORS)

什么是跨域资源共享(CORS)

跨域资源共享是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了自己以外的其他 origin (域、协议和端口),浏览器可以访问加载这些资源。

跨域资源共享还通过一种机制来检查服务器是否允许要发送真实请求,该机制通过浏览器发送一个到服务器托管的跨域源资源的 预检 请求。预检 中浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

http://domain-a.com 页面中对 https://domain-b.com 发送一个请求

跨域资源共享标准新增一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,对于那些可能对服务器数据产生副作用的 HTTP 方法( GET 以外的 HTTP 请求,或者搭配某些 MIME类型 的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。

场景

简单请求

不会触发 CORS 预检请求。若请求满足以下条件,则视为“简单请求”:

  • 使用 GETHEADPOST
  • 除了类用户代理自动设置的首部字段(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
    4
    var 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-MethodAccess-Control-Allow-HeadersOrigin

测试

  • 客户端:
    1
    2
    3
    4
    5
    var 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:响应的有效时间内浏览器不需要为同一请求再发送预检请求。

OPTIONS 请求标头
OPTIONS 响应标头

注意:

预检请求的成功仅限于 200~299 状态,其他状态会导致不会被共享或使 CORS 预检请求失败

附带身份凭证的请求

当发出跨源请求时,第三方 cookie 策略仍将适用

测试:

  • 客户端:
    1
    2
    3
    4
    5
    var 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-OriginAccess-Control-Allow-HeadersAccess-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