跨域资源共享CORS

1,994 阅读7分钟

本人是一个应届生,面试的时候经常会被问到跨域的问题,CORS当然也是解决跨域的方法之一了。但是当面试官继续问:“CORS跨域是怎么实现的?为什么会有OPTIONS请求呢?OPTIONS请求有什么用途呢?”可能回答的就不是那么完美。

所以,就总结归纳了以下关于CORS的详细知识。。。

参考文章:
跨域资源共享CORS详解
为什么会有OPTIONS请求
浏览器同源策略及其规避方法
HTTP访问控制(CORS)

什么是CORS?

CORS是W3C标准,全称“跨域资源共享(Corss-orign resource sharing)”。

它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持;整个CORS的通信过程都是浏览器自动完成的,用户不需要参与;CROS通信和同源的AJAX通信无差别;浏览器检测到AJAX请求跨源时会自动添加一些附加的头信息有时候还会多一次附加请求,但是用户不会有感觉。

CORS通信的关键还是服务器,只要服务器实现了CORS的接口,就可以跨源通信。

CORS分为两类

CORS请求分为两大类:简单请求和非简单请求,浏览器对这两种请求的处理是不一样的。

满足以下条件就是简单请求:

  • 请求方法:GETPOSTHEAD
  • 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指定的源不在许可的范围内:
服务器会返回一个正常的HTTP响应,但是这个响应的头信息没有包含Access-Control-Allow-Origin字段,会抛出错误,被XMLHttpRequestonerror回调函数捕获(!!这种情况状态码无法识别,返回的很可能是200)。

如果Origin指定的源在许可的范围内:
服务器返回的响应会多几个字段:

  • Access-Control-Allow-Origin:必须!要么是个域名,要么是个*,表示接受任何域名的请求。
  • Access-Control-Allow-Credentials:可选!是一个布尔值,表示允许发送cookie;默认情况下,cookie不包含在CORS请求中。该字段设为true时,表示服务器许可,cookie可以包含在请求中,一起发送给服务器。
  • Access-Control-Expose-Headers:可选!CORS请求时,XMLHttpRequest对象的getResponseHeader()只能拿到6个基本字段,Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段就要在Access-Control-Expose-Headers中指定,getResponseHeader('FooBar'),就可以返回FooBar的值。

withCredentials属性

上面说到CORS请求默认不会发送cookieHTTP的认证信息,所以要把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 requestsCORS中一种透明的服务器验证机制。预检请求首先会向另一个域名资源发送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-Typeapplication/json,后,按理说,应该是会发起预检请求。可实际结果与理论发生了冲突。

原因在于:预检请求需要在服务器中进行配置,在修改该路由的代码为以下内容后,浏览器正确地发起了OPTIONS预检请求。

const cors=require('cors');
router.options('/api/test/corsopt',cors());
router.post('/api/test/corsopt',cors(),(req,res)=>{
 res.end('test');
})

预检请求的回应

服务器接受了预检请求以后,会检查OriginAccess-Control-Request-MethodsAccess-Control-Request-Headers字段以后,确认允许跨域请求,就会做出回应。
如果浏览器否定了预检请求,会返回一个正常的HTTP响应,但是没有任何CORS相关的头信息字段。这时浏览器就会认为服务器不同意预检请求,因此触发一个错误,被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头信息字段。