如果还不了解跨域和同源策略的同学,可以去看一下第一篇,链接如下:跨域哪些事——1.如果没有跨域,世界会是怎样? - 文章 - 开发者社区 - 火山引擎 (volcengine.com)
首先我们看一下什么是CROS,跨域资源共享(英语:Cross-origin resource sharing,缩写:CORS),用于让网页的受限资源能够被其他域名的页面访问的一种机制。
由于同源策略跨域的限制,我们Ajax请求往往会被禁止,但是如果我们有一些可以确定是安全的跨域请求呢,这个时候我们该怎么办呢?我们知道有上一篇说的JSONP方式,想了解的可以看上一篇:跨域哪些事——3. 使用JSONP实现跨域 - 文章 - 开发者社区 - 火山引擎 (volcengine.com)。但是JSONP只支持get请求,很多时候特别是开发阶段,前端会起前端的项目,和后端的请求当然是跨域的,这时候JSONP就不行了,后端还是已POST请求为主的。于是我们CROS方案就出现了。
Tellme Networks 的马特·奥什里(Matt Oshry)、布拉德·波特(Brad Porter)与麦克·波德尔(Michael Bodell)于 2004 年 3 月提案将跨来源支持加入 VoiceXML 2.1以支持 VoiceXML 浏览器的跨来源资料请求。W3C 认为这不应该限制在 VoiceXML 而是一般的机制,因此将提案移到另一份实现备忘录。几个主要的浏览器厂商透过 W3C 的 Web 应用程序工作小组正式的将该备忘录改写为 W3C 工作草案并以推动成为 W3C 推荐标准为目标。
说到原理,如果让人理解其实很简单,就是W3C定义了一大堆规范,来对跨域做校验,让想跨域的浏览器和服务器都进行一定的校验,然后就可以完成跨域了,相当与在浏览器和服务器之间加了一个中间层。所以CROS跨域也只是浏览器和服务器之间的跨域,不能完成浏览器页面之间的跨域。
那到底是怎么校验的呢?分为2种,简单请求和非简单请求。以简单请求为例,凡是不同时满足下面面两个条件,就属于非简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2) HTTP的请求头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
比如我们常见的类型 application/json 现在看就不是简单请求。
2.1 简单请求
浏览器发现这次跨源AJAX请求是一般请求,就自动在头信息之中添加一个Origin字段。
#Origin字段用来说明本次请求来自哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请求。
GET /cors HTTP/1.1
Origin: [http://www.explem.com] #判断是否存在CORS安全问题
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.
而返回的响应会列出来现在的服务器允许哪些跨域的参数
响应
Access-Control-Allow-Origin: www.explem.com #请求时Origin字段的值或者是一个*(表示接受任意域名的请求)
Access-Control-Allow-Credentials: true #布尔值,表示是否允许发送Cookie(默认是否的)|浏览器不同则不同(有人说第一次请求都会发生),其次看服务器是否接收;
Access-Control-Expose-Headers: FooBar #CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段,为了能拿到字段就要设置
Content-Type: text/html; charset=utf-8
然后如果发的请求在允许范围内,就可以正常通信了。
2.2 非简单请求
非简单请求的CORS请求,会在正式通信之前增加一次HTTP查询请求,
称为"预检"请求(preflight);如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,
预检请求
OPTIONS /cors HTTP/1.1 #"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的
Origin: [http://www.explem.com](http://api.bob.com)
Access-Control-Request-Method: PUT #列出浏览器的CORS请求会用到哪些HTTP方法
Access-Control-Request-Headers: X-Custom-Header #逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
Host: api.alice.com
Connection: keep-alive
User-Agent: Mozilla/5.0
...
预检请求的回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: [http://www.explem.com] #关键点,该字段也可以设为星号,表示同意任意跨源请求。
Access-Control-Allow-Methods: GET, POST, PUT #关键点,它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Headers: X-Custom-Header #关键点,它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Max-Age: 1728000 #关键点,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒)
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
下面是”预检”请求之后浏览器的正常CORS请求,下面头信息中Access-Control-Allow-Origin字段是每次回应都必定包含的。
浏览器的正常请求和回应
PUT /cors HTTP/1.1
Origin: [http://www.explem.com] #默认是浏览器自动添加的
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
服务器正常的回应
Access-Control-Allow-Origin: [http://www.explem.com]
Content-Type: text/html; charset=utf-8
我们再看一下CROS这种方式。
首先他是当前前端和后端跨域的最佳方案。解决了JSONP不能发送POST请求的问题。
他的限制是需要新版本的浏览器支持(当然现在的浏览器都支持,只有一些老电脑不支持)。需要后端服务器添加对应的跨域规则。