天天看点

CORS跨域资源共享

在咱们的工作中,跨域比较常用的方式就是JSONP,JSONP就是通过script标签无同源限制的特点,在获取到需要的资源后自动执行回调方法的方式,这里我们要详细说的与这个不同,是浏览器原生的CORS跨域,是通过“正当手段”得到服务器姐姐首肯,大摇大摆获取跨域资源的方式,相比JSONP只能实现GET请求,CORS大法支持所有的类型,同时CORS是通过普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理,接下来我们就来说下这个CORS大法。

简介

CORS(Cross-Origin Resource Sharing)跨域资源共享,主要思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定响应是成功还是失败,它允许了浏览器向跨源服务器发送请求,从而克服了同源的限制。

简略的来说,原理就是向服务器发送请求跨域资源的时候,如果是简单请求(复杂请求会发出一个额外的preflight,后面详说),浏览器会自动在头信息中增加一个Origin字段告诉服务器,本次请求来自哪个源(协议 + 域名 + 端口),服务器如果同意,就会在返回的头部信息中增加一个Access-Control-Allow-Origin字段,把Origin中的信息写到这个字段的值中,告诉浏览器,我答应你请求我啦~

整个CORS的通信过程,都是浏览器自动完成的。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头部信息,复杂请求像是PUT,DELETE等的情况还会多一次附加的请求,具体内容下面会详细解释

CORS 浏览器的支持情况

CORS需要浏览器和服务器的同时支持,目前浏览器的支持情况如下:

CORS跨域资源共享

浏览器端已经获得了良好的支持,所以实现CORS的关键就是服务器,只要实现了CORS的接口,就可以实现跨域通信。

IE对CORS的实现

IE8中引入了XDR(XDomainRequest),这个对象与XHR类似,不同之处如下:

1. cookie不会随请求发送,也不会随响应返回

2. 只能设置请求头部信息中的Content-Type字段

3. 不能访问响应头部信息

4. 只支持GET和POST请求

XDR对象的使用方法用户XHR对象非常类似,如下:

var xdr = new XDomainRequest();
xdr.onload = function() {
    alert(xdr.responseText);
}
xdr.onerror = function() {
    alert("error");
}
xdr.open("get", "http://www.xxx.com/yyy/");
xdr.send(null);
           

XHR的open方法第三个参数接受一个是否异步发送请求的bool值,XDR则不接收第三个参数,请求都是异步执行的,请求返回后出发load并将响应数据保存在responseText属性中。XDR无法确定响应的状态码,响应有效会触发load,失败就会出发error,但却没有任何错误信息可用,只能知道请求是否成功.

其他浏览器对CORS的实现

Firefox3.5+,Safari4+,Chorme,IOS版的Safari和Android平台下的WebKit都通过XmlHttpRequest实现了对CORS的支持。在尝试请求跨域资源的时候,自动就会触发这个行为。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if(xhr.readyState == ){
        if(xhr.status >=  && xhr.status <  || xhr.status == ){
            console.log(xhr.responseText)
        }else {
            console.log('err' + xhr.status);
        }
    }
};
xhr.open('get','http://www.xxx.com/zzz/',true);
xhr.send(null);
           

XHR相比XDR,可以访问status和statusText,同时支持同步请求。跨域XHR也有一些安全限制:

  1. 不能使用setRequestHeader()设置自定义头部
  2. 不能发送和接收cookie
  3. 调用获取所有头部信息的方法getAllReponseHeaders()方法会返回空字符串

简单请求与复杂请求

浏览器将CORS请求分成两类。

简单请求:

Simple requests

A simple cross-site request is one that meets all the following conditions:

The only allowed methods are: GET HEAD POST

Apart from the headers set automatically by the user agent (e.g. Connection,User-Agent, etc.), the only headers which are allowed to be manually set are:

Accept

Accept-Language

Content-Language

Content-Type

The only allowed values for the Content-Type header are:

application/x-www-form-urlencoded multipart/form-data text/plain

大意就是说:

1. 请求方法是以下三种方法之一:HEAD,GET,POST

2. HTTP的头信息不超出以下几种字段:

Accept

Accept-Language

Content-Language

Last-Event-ID

3. Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

非简单请求:

非简单请求是那种对服务器有特殊要求的请求,除以上条件之外的都是非简单请求,要求必须首先使用 OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响,条件如下:

  1. 使用了下面任一 HTTP 方法:
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  2. 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (but note the additional requirements below)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  3. Content-Type 的值不属于下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

CORS对不同请求的处理

简单请求:

对于简单的跨域请求,浏览器自动的发出CORS请求,在请求头中,增加一个Origin字段,如下请求头:

GET /cors HTTP/1.1
Origin: http://m.zhuanzhuan.58.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

Origin字段用来说明本次请求来自哪个源(协议 + 域名 + 端口)

服务器接收到请求后,如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP回应,浏览器接收到的回应没有头信息中没有包含Access-Control-Allow-Origin字段,那么浏览器就会抛出一个错误,被XHR的onerror函数捕捉,这种情况无法通过状态码判断,状态码可能会返回200。

如果Origin指定的域名在许可的范围内的话,服务器返回的响应,会多出来几个头信息字段:

Access-Control-Allow-Origin:  http://m.zhuanzhuan.58.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: TextParam
Content-Type: text/html; charset=utf-
           

前三个以Access-Control-开头的字段都是与CORS相关的字段

  1. Access-Control-Allow-Origin 该字段是允许跨域响应头中必不可少的一个字段,值一般都是请求中Origin的值,表示允许当前源的跨域请求,也可以设置成*,表示接受任意源的请求。
  2. Access-Control-Allow-Credentials表示是否允许跨域发送cookie,改属性下面会详解
  3. Access-Control-Expose-Headers,该属性返回的字段表示,用户可拿到的头部中除了基本的留个字段之外的其他字段的字段名称。在浏览器发送请求时,可以通过使用setRequestHeader()方法设置自定义头部信息,也可以通过getReponseHeader()方法获取响应的响应头部信息(上面提到的getAllReponseHeaders()是获取响应头所有基本字段),而正常情况下,获取响应头部信息只能拿到7个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到别的字段,就需要在Access-Control-Expose-Headers中传入一下,像上面的返回头,就可以通过getResponseHeader(‘TextParam’)返回TextParam字段的值。

非简单请求 Preflighted Request:

CORS通过Preflighted Request的透明服务器验证机制支持开发人员使用自定义的头部。对于那些基本方法以外的请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(Rreflighted Request),从而获知服务端是否允许该跨域请求。

预检HTTP请求头部如下:

OPTIONS /cors HTTP/1.1
Origin:  http://m.zhuanzhuan.58.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: TextParam
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           
  1. Origin:与简单的请求相同
  2. Access-Control-Request-Method: 必须字段,表示请求自身使用的方法
  3. Access-Control-Request-Headers:可选字段,自定义的头部信息,多个头部以逗号分隔,指浏览器CORS请求会额外发送的头信息字段。上例是带有自定义头部TextParam。

请求发送之后,如果服务器允许该源对资源的请求,会作出响应:

HTTP/  OK
Access-Control-Allow-Origin: http://m.zhuanzhuan.58.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: TextParam
Access-Control-Max-Age: 
...
           
  1. Access-Control-Allow-Origin:表示该请求来源可以请求数据
  2. Access-Control-Allow-Methods: 必须字段,表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求。
  3. Access-Control-Allow-Headers:可选字段,表明服务器允许请求中携带字段 TextParam 与 Content-Type,逗号分割。
  4. Access-Control-Max-Age:可选字段,用来指定本次预检请求的有效期,单位为秒,在此期间浏览器无需为同一请求再次发送预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

服务器一旦通过了预检请求,以后每次浏览器正常的CORS请求都会跟简单请求一样。

如果浏览器不同意该预检请求,会返回一个正常的HTTP回应,但是没有任何的CORS相关头信息字段,这时浏览器接收到响应,会被XMLHttpRequest对象的onerror()回调函数捕获,控制台会打出如下的报错信息。

XMLHttpRequest cannot load http://i.zhuanzhuan.com.
Origin http://m.zhuanzhuan.com is not allowed by Access-Control-Allow-Origin.
           

携带凭据的请求

默认情况下跨域请求不提供凭据(Cookie,HTTP认证以及SSL证明等)。通过将xhr的withCredentials属性设置为true,可以指定某个请求发送凭据。如果服务器接受带凭据的请求,会用下面的Http头部来响应。

如果服务器响应不包含这个头或者为false,那么浏览器就不会把响应交给javascript,responseText为空字符串,status为0,触发onerror()。

服务器可以在预检Preflight响应头中返回这个字段表示允许源发送携带凭据的请求。

支持的浏览器有FireFox3.5+,Safari4+和Chrome。IE10及以下都不支持。

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”,值必须为Origin 首部字段所指明的域名即允许附带凭证的源。

继续阅读