转自
本人是一个应届生,面试的时候经常会被问到跨域的问题,CORS当然也是解决跨域的方法之一了。但是当面试官继续问:“CORS跨域是怎么实现的?为什么会有OPTIONS请求呢?OPTIONS请求有什么用途呢?”可能回答的就不是那么完美。
所以,就总结归纳了以下关于CORS的详细知识。。。
参考文章:
什么是CORS?
CORS
是W3C标准,全称“跨域资源共享(Corss-orign resource sharing)”。
它允许浏览器向跨源服务器发送XMLHttpRequest
请求,从而克服了AJAX
只能同源使用的限制。
CORS
需要浏览器和服务器同时支持;整个CORS
的通信过程都是浏览器自动完成的,用户不需要参与;CROS
通信和同源的AJAX
通信无差别;浏览器检测到AJAX
请求跨源时会自动添加一些附加的头信息有时候还会多一次附加请求,但是用户不会有感觉。
CORS
通信的关键还是服务器,只要服务器实现了CORS
的接口,就可以跨源通信。
CORS分为两类
CORS请求分为两大类:简单请求和非简单请求,浏览器对这两种请求的处理是不一样的。
满足以下条件就是简单请求:
- 请求方法:
GET
,POST
,HEAD
- HTTP头信息不超过以下几种:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:application/x-www-form-urlencode、mulitpart/form-data、text/plain(只限于这三个值)
简单请求
对于简单请求,浏览器会直接发出CORS
请求;浏览器发现此次跨源的AJAX
请求是简单请求,就自动在请求头信息中添加Origin
字段来说明本次请求来自哪个源(域名+端口+协议);服务器根据这个值来决定是否同意这次请求。
如果Origin
指定的源在许可的范围内:
Access-Control-Allow-Origin
字段,会抛出错误,被XMLHttpRequest
的onerror
回调函数捕获(!!这种情况状态码无法识别,返回的很可能是200
)。 如果Origin
指定的源不在许可的范围内:
- Access-Control-Allow-Origin:必须!要么是个域名,要么是个*,表示接受任何域名的请求。
- Access-Control-Allow-Credentials:可选!是一个布尔值,表示允许发送
cookie
;默认情况下,cookie
不包含在CORS
请求中。该字段设为true
时,表示服务器许可,cookie
可以包含在请求中,一起发送给服务器。 - Access-Control-Expose-Headers:可选!
CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
只能拿到6个基本字段,Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
。如果想拿到其他字段就要在Access-Control-Expose-Headers
中指定,getResponseHeader('FooBar')
,就可以返回FooBar
的值。
withCredentials属性
上面说到CORS
请求默认不会发送cookie
和HTTP
的认证信息,所以要把cookie
发送到服务器不但要服务器同意设置
Access-Control-Allow-Credentials:true 复制代码复制代码
还要开发者在AJAX请求中设置withCredentials
属性
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; 复制代码复制代码
否则,即使服务器同意发送cookie
,浏览器也不会发送。 !!!如果要发送cookie
,那么Access-Control-Allow-Origin
的值不能是*
,必须是指定明确的,与请求网页一致的域名;同时,cookie
依然遵循同源策略,只有用服务器域名设置的cookie
才会上传,其他域名的cookie
不会上传,且(跨源)原网页代码中document.cookie
也无法读取服务器域名下的cookie
。
非简单请求
当满足下面任意条件时,会发送预检请求:
- 使用了下面的任意方法:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 人为设置了对CORS安全的首部字段集合的其他首部字段,该集合有:
- Accept
- Accept-Language
- Content-Language
- Content-Type(but note the addtional requirements below)
- DPR
- Downlink
- Save-Data
- Viewport-width
- Width
- Content-Type的值不属于下列之一:
- application/x-www-form-urlencode
- mulitpart/form-data
- text/plain
预检请求Preflighted Requests
非简单请求的CORS
请求,会在正式通信之前,增加一次HTTP
查询请求(‘预检’请求)。preflighted requests
是CORS
中一种透明的服务器验证机制。预检请求首先会向另一个域名资源发送HTTP
OPTIONS
请求头,来验证发送的请求是否安全。
浏览器先询问服务器,当前网页所在的域名是否在服务器许可的名单之中,以及可以使用哪些HTTP
动词和头信息字段。只有得到肯定的答复,浏览器才会发出正式的XMLHttpRequest
请求,否则会报错。
浏览器发现,是一个非简单请求,就自动发送一个“预检”请求,要求服务器确认。
预检请求的请求方法是OPTIONS
,表示询问;头信息里的关键字是Origin
,表示请求来自哪个源除了Origin
还有两个字段:
- Access-Control-Request-Method:列出浏览器的CORS请求会用到哪些HTTP方法。
- Access-Control-Request-Headers:指定浏览器CORS请求会额外发送的头信息字段。
OPTIONS请求的用途?
1、获取服务器支持的HTTP请求方法。2、用来检查服务器的性能。复制代码
AJAX进行跨域请求时的预检,需要向另一个域名的资源发送一个HTTP OPTIONS的请求头, 用以判断实际的请求是否安全。(这个是浏览器加上的,后端没有做任何操作) 复制代码复制代码
为什么会有OPTIONS请求?
规范要求,对那些可能对服务器数据产生副作用的HTTP
请求方法(特别是GET
以外的HTTP
请求,或搭配某些MIME
类型的POST
请求),浏览器必须首先使用OPTIONS
发送一个预检请求,来获取服务器是否允许该跨域请求。服务器确认允许跨域后,浏览器再发送实际的HTTP
请求。
为什么没有发生预检请求?
在跨域请求服务器时,设置了Content-Type
为application/json
,后,按理说,应该是会发起预检请求。可实际结果与理论发生了冲突。 原因在于:预检请求需要在服务器中进行配置,在修改该路由的代码为以下内容后,浏览器正确地发起了OPTIONS预检请求。
const cors=require('cors');router.options('/api/test/corsopt',cors());router.post('/api/test/corsopt',cors(),(req,res)=>{ res.end('test');})复制代码复制代码
预检请求的回应
服务器接受了预检请求以后,会检查Origin
,Access-Control-Request-Methods
,Access-Control-Request-Headers
字段以后,确认允许跨域请求,就会做出回应。
XMLHttpRequest
对象的onerror
函数捕获。该错误信息为: XMLHttpRequest cannot load http://xxx.xxx.com. Origin http://xxx.xxx.com is not allowed by Access-Control-Allow-Origin.复制代码复制代码
服务器响应的其他CORS相关字段:
- Access-Control-Request-Methods:列出浏览器的CORS请求会用到哪些HTTP方法(返回的是所有支持的方法)
- Access-Control-Request-Headers:表示服务器支持的所有头信息字段,不限于浏览器在“预检”请求的字段。
- Access-Control-Allow-Credential:与简单请求的意义相同。
- Access-Control-Max-Age:可选!本次预检请求的有限期,该期限期间不会再发送预检请求。
浏览器的正常请求和回应
一旦服务器通过了“预检”请求,以后每次浏览器正常的CORS请求,都和简单请求一样,会有一个Origin
头信息字段;服务器响应也是一样会有一个Access-Control-Allow-Origin
头信息字段。