CORS

cross-origin resource sharing 跨域资源共享,允许web应用程序进行跨域访问控制,从而使跨域数据安全传输。
该方案不支持IE10以下,更多的跨域方案可参考 跨域方式

什么情况下需要CORS?

跨域资源共享标准(cross-origin sharing standard)允许在以下场景中使用跨域HTTP请求:

  • XMLHttpRequestFetch发起的跨域请求
  • Web 字体 (CSS 中通过@font-face使用跨域字体资源)
  • WebGL 贴图
  • 使用 [drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) 将 Images/video 画面绘制到 canvas
  • 样式表(使用 CSSOM

功能概述

跨域资源共享标准新增了一组HTTP头部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,对那些可能对服务器产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求。服务器允许后才发起实际的HTTP请求。预检请求的返回中,服务器也可以通知客户端是否需要携带身份凭证(包括Cookies和HTTP认证相关数据)。

两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。简单请求不会触发CORS预检请求。

简单请求

需同时满足以下条件:

  1. 请求方法:GET、HEAD、POST
  2. 人为设置了以下HTTP的头部信息之外的其他头部字段:
  • Accept
  • Accept-Language
  • [Content-Language](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Language)
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
  1. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  2. 请求中没有使用 ReadableStream 对象。

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。响应中携带了响应首部字段 [Access-Control-Allow-Origin](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) 。使用 OriginAccess-Control-Allow-Origin 就能完成最简单的访问控制。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

非简单请求

不满足简单请求的条件即为非简单请求,需要先发送OPTIONS预检请求,预检请求完成后才发送实际的请求,就跟简单请求一样,会有一个Origin头部,服务器相响应中会有Access-Contrl-Allow-Origin头部字段。

HTTP请求首部字段

可用于发起跨域请求的首部字段,这些字段无需手动设置。

Origin(简单、非简单)

表明预检请求或实际请求的源站。它不包含任何路径信息,只是服务器名称。不管是否为跨域请求,ORIGIN 字段总是被发送。

  1. Origin: <origin>

Access-Control-Request-Method(非简单)

用于预检请求,将实际请求所用的HTTP方法告诉服务器。

  1. Access-Control-Request-Method: <method>

Access-Control-Request-Headers(非简单)

用于预检请求,将实际请求所携带的首部字段告诉服务器。

  1. Access-Control-Request-Headers: <field-name>[, <field-name>]*

HTTP响应首部字段

Access-Control-Allow-Origin(简单、非简单)

  1. Access-Control-Allow-Origin: <origin> | *

如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

Access-Control-Allow-Methods(非简单)

  1. Access-Control-Allow-Methods: <method>[, <method>]*

用于预检请求的响应。指明实际请求所允许的HTTP方法。

Access-Control-Allow-Headers(非简单)

  1. Access-Control-Allow-Headers: <field-name>[, <field-name>]*

用于预检请求的响应。指明实际请求所允许携带的首部字段。

Access-Control-Allow-Credentials(简单、非简单)

它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。
如果此头部字段在预检请求的响应中时,如果为true则表示实际请求的cookie可以包含在请求中,一起发送给服务器。

  1. Access-Control-Allow-Credentials: true

如果要把Cookie发送到服务器,服务器需指定

  1. Access-Control-Allow-Credentials:true

并且请求中需打withCredentials

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

简单GET请求不会被预检,如果对此类请求的withCredentials设置为true,而且响应中不包含该字段,这个响应 将被忽略,并且浏览器也不会将响应的内容返回给网页。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials

  1. xhr.withCredentials = false;

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

Access-Control-Expose-Headers

跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma,如果要访问其他头,则需要服务器设置本响应头。

此头部字段让服务器把允许浏览器访问的头放入白名单。

  1. Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样浏览器就能够通过getResponseHeader访问X-My-Custom-HeaderX-Another-Custom-Header 响应头了

Access-Control-Max-Age(非简单)

  1. Access-Control-Max-Age: <delta-seconds>

指定预检请求的结果能够被缓存多久。delta-seconds 参数表示preflight请求的结果在多少秒内有效,在此期间,浏览器不会发送另一条预检请求。

参考

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
http://www.ruanyifeng.com/blog/2016/04/cors.html