前端工程师须知的CORS知识

背景

前端和后台请求交互,若请求者(Client)和资源者响应者(Server)处在不一样的域名,会发生跨域请求,不一样域是指HTTP协议、域名、端口有一个或全都不相同。出于安全考虑浏览器限制了从脚本内发起的资源跨域请求,CORS是解决跨域请求的一种机制。html

1、什么CORS

CORS全称“跨域资源共享”,它是一种机制经过添加额外的HTTP头部告诉浏览器,容许origin上的web应用访问不一样源服务器资源,能够在XMLHttpRequest、Fetch中使用CORS发起跨域请求。前端

2、CORS工做原理

对于前端开发使用CORS发起跨域请求不须要额外工做,当请求发生时浏览器会自动设置相应的HTTP头部信息(下面介绍道),服务器端须要作相应的头部设置来控制跨域权限。web

1. 发起请求

对于使用CORS发起跨域请求可分为简单请求和非简请求两种,简单请求不会触发OPTIONS预检请求,可直接发起跨域请求,非简单请求会触发OPTIONS预检请求,向服务器发起一些询问信息,例如:是否容许此次跨域请求、告知用什么请求方法、是否支持在HTTP头里携带自定义字段。跨域

1.1 简单请求

简单请求可直接向服务器请求,只要服务器赞成此次跨域求,浏览器就能获取到服务器返回的资源,在请求中只要同时知足如下条件就属于简单请求。浏览器

a. 使用GET、POST、HEAD 三者之一的请求方法。缓存

b. 不得设置Accept 、 Accept-Language、 Content-Language,Content-Type以外的头部信息集合字段,对于Content-Type的值不能设置text/plain, multipart/form-data, application/x-www-form-urlencode以外的 值。安全

c. 请求中的任意XMLHttpRequestupload对象均未设置任何监听器。bash

d. 请求中没有使用 ReadableStream 对象。服务器

简单例子

var request = new XMLHttpRequest()
var url = 'http://bar.other/resources/public-data/'

function callOtherDomain() {
  if(invocation) {    
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}
//发起请求
callOtherDomain()
复制代码

请求发出时Client与Server间经过HTTP头部字段处理跨域权限。cookie

HTTP请求头信息
1. GET /resources/public-data/ HTTP/1.1
2. Host: bar.other
3. User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; 
4. rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
6. Accept-Language: en-us,en;q=0.5
7. Accept-Encoding: gzip,deflate
8. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
9. Connection: keep-alive
10. Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
11. Origin: http://foo.example
复制代码
服务器响应头部信息
1. HTTP/1.1 200 OK
2. Date: Mon, 01 Dec 2008 00:23:53 GMT
3. Server: Apache/2.0.61 
4. Access-Control-Allow-Origin: *
5. Keep-Alive: timeout=2, max=100
6. Connection: Keep-Alive
7. Transfer-Encoding: chunked
8. Content-Type: application/xml
复制代码

Client经过Origin字段告诉Server我是来自http://foo.example的请求,容许我访问你的资源吗?Server作出回应并在响应头里带上Access-Control-Allow-Origin:* 表示我容许全部的外域访问个人资源。

若是将Access-Control-Allow-Origin值设置成http://foo.example,表示Server上的资源只容许来自http://foo.example的源访问。后台开发通常会在此添加请求源白名单来控制容许那些请求源访问服务资源。

若是Origin不是Access-Control-Allow-Origin容许的源,浏览器将拦截请求响应内容,没法获取到服务器资源。

1.2 非简单请求

只要不知足简单请求的都属于非简单请求。非简单请求会在正式发起资源访问请求前触发一个options 预检请求,options请求不会对服务器形成资源影响。 只要知足下列条件之一就会触发预检请求:

a. PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH。

b. 在请求头中设置Accept Accept-Language、 Content-Language、 Content-Type (须要注意额外的限制)、 DPR、 Downlink、 Save-Data、 Viewport-Width、 Width的字段。

c. Content-Type 的值不属于application/x-www-form-urlencoded、 multipart/form-data、 text/plain三者之一。

d. 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。

e. 请求中使用了ReadableStream对象。

再次借用MSDN中的例子说下

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
    
function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}
// 发起请求
callOtherDomain()
复制代码

在代码中能够看到,咱们人为的自定义了X-PINGOTHER请求头字段(知足非简单请求b条件),而且Content-Type被设置application/xml (知足非简单请求c条件),因此该请求首先会触发一个options请求。

下面是options请求头的部分信息:

1.OPTIONS /resources/post-here/ HTTP/1.1
 2.Host: bar.other
 3.User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
 4.Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 5.Accept-Language: en-us,en;q=0.5
 6.Accept-Encoding: gzip,deflate
 7.Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 8.Connection: keep-alive
 9.Origin: http://foo.example
10.Access-Control-Request-Method: POST
11.Access-Control-Request-Headers: X-PINGOTHER, Content-Type
复制代码

options 请求告诉Server, 我是来自http://foo.example的请求,在正式请求时,我会在请求头中携带自定义字段X-PINGOTHER、Conten-Type而且我将经过POST发起请求,你赞成不?如下是用于向Server询问的头部信息字段:

Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
复制代码

咱们看下Server对options请求是如何回应的

1.HTTP/1.1 200 OK
2.Date: Mon, 01 Dec 2008 01:15:39 GMT
2.Server: Apache/2.0.61 (Unix)
3.Access-Control-Allow-Origin: http://foo.example
4.Access-Control-Allow-Methods: POST, GET, OPTIONS
5.Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
6.Access-Control-Max-Age: 86400
7.Vary: Accept-Encoding, Origin
8.Content-Encoding: gzip
9.Content-Length: 0
10.Keep-Alive: timeout=2, max=100
11.Connection: Keep-Alive
12.Content-Type: text/plain
复制代码

从上面的options响应头里得知, 首部字段 Access-Control-Allow-Origin代表服务器容许http://foo.example请求源访问资源。 字段 Access-Control-Allow-Headers 代表服务器容许请求头中携带字段 X-PINGOTHER 与 Content-Type。 Access-Control-Allow-Methods告知容许正式请求使用POST方法。 options请求结束,若是Server赞成访问,接下来就会发起正式的请求,不然结束请求。

第6行代码中有一段Access-Control-Max-Age: 86400信息,这段信息的意思是说在接下来的86400秒内对于同一请求不用再发起options预检请求。Access-Control-Max-Age单位是秒,由后台设置但不能超过浏览器支持的最大有效时间,不然不会生效。

2. 附带身份凭证的请求

Fetch 与 CORS 能够基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。通常而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。

若是要发送凭证信息,须要设置 XMLHttpRequest 的withCredentials=true,同时Server端也要设置响应的头部字段Access-Control-Allow-Credentials: true,才能在请求中携带身份凭证,不然,浏览器会拦截响应内容,不会把它返回给请求发送者。

须要注意,Server若是设置了Access-Control-Allow-Origin:*同时请求又携带了cookies信息,会致使请求失败。

3. 跨域请求头和响应头字段说明

3.1 请求头字段

Origin字段代表预检请求或实际请求的源站,origin 参数的值为源站 URI。它不包含任何路径信息,只是服务器名称,无论是否为跨域请求都会发送Origin字段。

Origin: <origin>
复制代码

Access-Control-Request-Method字段用于预检请求。其做用是,将实际请求所使用的 HTTP方法告诉服务器。

Access-Control-Request-Method: <method>
复制代码

Access-Control-Request-Headers字段用于预检请求。其做用是,将实际请求所携带的首部字段告诉服务器。

Access-Control-Request-Headers: <field-name>[, <field-name>]*
复制代码

3.2 响应头字段

Access-Control-Allow-Origin字段,origin 参数的值指定了容许访问该资源的外域 URI。对于不须要携带身份凭证的请求,服务器能够指定该字段的值为通配符,表示容许来自全部域的请求。

Access-Control-Allow-Origin: <origin> | *
复制代码

Access-Control-Expose-Headers字段,设置浏览器能够拿到的头部字段白名单。 在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,若是要访问其余头,须要服务器设置本响应头,这就是Access-Control-Expose-Headers的做用。

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
复制代码

Access-Control-Max-Age字段,设置预检测请求能缓存多久,单位秒。

Access-Control-Max-Age: <delta-seconds>
复制代码

Access-Control-Allow-Credentials字段,指定了当浏览器的credentials设置为true时是否容许浏览器读取response的内容

Access-Control-Allow-Credentials: true
复制代码

Access-Control-Allow-Methods字段用于预检请求的响应。其指明了实际请求所容许使用的 HTTP 方法。

Access-Control-Allow-Methods: <method>[, <method>]*
复制代码

Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中容许携带的首部字段。

Access-Control-Allow-Headers: <field-name>[, <field-name>]*
复制代码

4.兼容性

目前除了IE9及如下浏览不支持CORS外,其余浏览器基本的已经支持CORS。对于IE9及如下的浏览器可经过 XDomainRequest实现。

以上是我对CORS知识的梳理和记录,由于在以往工做中频繁遇到跨域问题,思来想去决定记录下来,为确保信息的正确性,极大的参照来官方文档,但愿对有须要的小伙伴在处理跨域问题上有所帮助。

声明:本文的内容参考于MDN官方文档,包括上面用到的事例代码也是借用自MDN,如有侵权,请联系我删除相关内容。

相关文章
相关标签/搜索