CORS 是什么?

跨域资源共享 是一种机制,它使用额外的头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

什么情况下需要使用CORS?

  1. XMLHttpRequest 和 fetch 请求
  2. WebGL
  3. Web字体
  4. 使用 drawImg 将 image/video 绘制到 canvas 上
  5. 样式表

CORS 请求的规范

当浏览器检测到某个请求跨域之后, 就会根据请求的类型发送CORS规定的网络请求。具体是指,简单请求和非简单请求具有差异性。

对于简单请求, 只要服务端允许跨域, 具体是设置了Access-Control-Allow-Origin字段为*。这样会带来安全问题, 后面会继续讨论。

对于非简单请求, 一般都会对服务器的数据进行一些操作, 所以会首先发起预检请求,询问服务器是否允许这次的访问。如果允许, 在发起正常的网络请求。

预检请求

使用HTTP1.1的OPTIONS方法发起预检请求, 这个请求用于向服务端获取更多的信息, 但是不会对服务器的资源造成影响。
预检请求会携带一下两个字段:

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。服务器据此决定,该实际请求是否被允许。

预检请求的响应

浏览器发起预检请求之后,服务器发送回响应。响应携带如下重要字段:

Access-Control-Allow-Origin: http://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-Methods: 表示允许请求的方法有POST, GET, OPTIONS
Access-Control-Allow-Headers: 请求允许的头部信息类型为: X-PINGOTHER, Content-Type, 逗号分割列表。
Access-Control-Max-Age: 此次预检请求的有效期, 86400秒, 在有效期内, 发送相同请求不需要再发送预检请求,如果超过这个时间, 对于同一个请求, 需要再次发起预检请求。

HTTP 首部响应字段

Access-Control-Allow-Origin: <origin>: origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符(*),表示允许来自所有域的请求。
Access-Control-Max-Age: <delta-seconds>: 指定一次预检请求的有效期。有效期内不会再次发送预检请求。
Access-Control-Allow-Methods: <method>[, <method>]*: 允许请求的方法(get, post ...)
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header: 服务器把允许浏览器访问的头放入白名单.
在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。
Access-Control-Allow-Headers: <field-name>[, <field-name>]*: 预检响应告知浏览器允许的实际请求头类型。

CORS withCredentials 属性

CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意, 另一方面,开发者必须在AJAX请求中打开withCredentials属性。
server:

Access-Control-Allow-Credentials: true

developer:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

二者缺一不可。
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

HTTP 首部请求字段

Origin: <origin>: 表示请求的源, origin 参数的值为源站 URI。它不包含任何路径信息,只是服务器名称。不管是否为跨域请求,ORIGIN 字段总是被发送。
Access-Control-Request-Method: <method>: 将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Headers: <field-name>[, <field-name>]*: 将实际请求所携带的首部字段告诉服务器。

以上, 来自MDN。

浏览器阻止跨域的两种方式

  1. 请求正常发起, 但是返回的数据被浏览器拦截
  2. 请求发起的时候被浏览器拦截

CORS 的隐患

CSRF(Cross-site request forgery)跨站请求伪造
利用可跨域这一特性。

image.png