CORS 跨域资源共享
浏览器跨域问题相信大家已经不止一次遇到了,不论是指定document.domain还是使用jsonp,都让人感觉不爽。
当当当当!CORS(Cross-Origin Resource Sharing)就是专门为解决这个问题而生的!
一、 CORS的工作流程
CORS在获取跨域资源的时,一共分为两步:
- 发送一个
预检请求【Preflight Request】
来询问服务器即将发生的跨域请求是否被允许 - 如果跨域请求被允许,则发送真实的跨域请求,否则阻止跨域
二、 CORS预检请求
1. CORS预检请求是一个OPTIONS请求
为什么偏偏是 OPTIONS
这个大家不太常用的谓词呢?
我们来看下OPTIONS的官方定义:
OPTIONS方法是用于请求获得由Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。
而预检请求呢,顾名思义,就是在真实跨域请求之前,预先发送的一个检测跨域是否被允许的请求。
所以,这也就解释的通了。
2. CORS预检请求必须快
废话,难道不是每个请求都是越快越好吗,为毛要强调预检请求?他又什么不同嘛?
上面我们已经说过,在CORS的跨域过程中,由于要先检测跨域是否被允许,这样就相当于一次跨域的过程中发起了两次请求。大家都知道,web前端优化提升最大最显著的是就是减少请求数,因为页面渲染以及脚本执行效率跟发送一次请求耗费在网络上的时间比起来简直是小巫见大巫。所以这次预检请求必须要快,要足够快!
那么,为了变得更快,CORS预检请求做了哪些优化呢?
- 不携带任何请求正文
- 只携带几个Request Headers
- 预检请求结果可以被缓存,缓存失效前将不再发送相同的预检请求
CORS预检请求携带的 Request Headers 示例:
Headers | Description |
---|---|
Access-Control-Request-Method | 真正跨域请求时的HTTP谓词 |
Access-Control-Request-Headers | 真正跨域请求时携带的自定义Header列表 |
Origin | 当前页面所在站点 |
3. CORS预检请求的响应
好哒,知道了这些,服务器端就可以来判断是否应该允许接下来真正的跨域请求啦,如果允许呢,就返回 200 OK
,否则就返回 400 Bad Reuqest
。
上面说过,浏览器端为了让CORS预检请求快起来,做了那么多努力,服务器端怎么好意思一下返回一个好几兆那么 大的消息正文让浏览器的努力白费呢?不但不能让他白费,还得尽力支持才是呀。
所以:
- 预检请求的响应也是不携带消息正文的
- 预检请求的响应应该尽可能携带一个缓存标记,指示浏览器缓存预检结果
CORS预检请求响应携带的 Response Headers 示例:
Headers | Description |
---|---|
Access-Control-Allow-Origin | 指定允许跨域请求的站点[1] |
Access-Control-Allow-Methods | 指定允许跨域请求的HTTP谓词[1] |
Access-Control-Allow-Headers | 指定允许跨域请求携带的Headers[2] |
Access-Control-Allow-Credentials | 指定服务器端是否支持用户凭证[3] |
Access-Control-Max-Age | 指定此预检结果的缓存时间(单位:秒) |
注意【1】:通常在服务器端会维护一个允许跨域请求的站点列表及其对应的谓词和消息头的信息,如果预检结果为200 OK
的话,没必要把所有允许的站点、谓词和Headers写到ResponseHeaders里面,只返回预检请求Request中携带的信息就好。举个例子就是,“A问B:我可以去你家吗?B回答:你可以”。就这样就好了,并不需要回答:“你,小明和小刚都可以”。
注意【2】:Access-Control-Allow-Headers
中所指定的消息头必须与预检请求中携带的消息头完全一致,而且这些Headers将会在接下来发起的真正的跨域请求中携带并发送给服务器端。
注意【3】:Access-Control-Allow-Credentials
的值为 true
时,表明服务器支持用户凭证,此时,真正的跨域请求将携带Cookie
、HTTP-Authentication
报头以及客户端X.509
证书;需要跟 withCredentials
配合使用。将在下面详细说明。
三、 CORS跨域请求中的身份验证
有了CORS跨域资源共享,我们在浏览器端调用各种webapi简直比狗还要方便了!但是,等等!这里有个问题!
Q: CORS跨域资源共享到底支不支持用户身份的验证呢?如果不支持,那可真是太糟糕了。。。
A: 支持哒。 ( ̄▽ ̄")
我们先上一段javascript:
$.ajax({
url: 'http://api.Magic-conch.cool/api/xxxxxx',
type: 'GET',
xhrFields: {
withCredentials: true
},
success: function (data, statusCode) {
}
});
嗯,这段代码大家都看得懂,就不细说啦。但是在发起ajax请求的时候,指定了一个 xhrField: withCredentials: true
。
withCredentials: true
是什么鬼呢?好像在哪见过?
没错,就是刚刚才说过的预检结果Headers中的 Access-Control-Allow-Credentials
!
简单来说呢,当预检结果中存在一个值为 true
的 Access-Control-Allow-Credentials
消息头,并且,这个跨域请求是由客户端JavaScript程序利用一个withCredentials属性为true的XMLHttpReuqest发送的
那么! 服务器端就可以获取到cookie啦,然后我们就可以愉快的做身份验证啦!