谈跨域资源共享之 CORS

CORS 即 Cross-origin resource sharing,跨域资源共享 ,是由 W3C 官方推广的允许通过 AJAX 技术跨域获取资源的规范 。
CORS 简介
跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
跨源HTTP请求的一个例子:运行在 http://www.infoq.cn 的 JavaScript 代码使用XMLHttpRequest来发起一个到 https://www.infoq.cn/data.json 的请求。
出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确 CORS 响应头。
 
 跨源域资源共享( CORS )机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨源 HTTP 请求所带来的风险。
什么情况下需要 CORS ?
这份 cross-origin sharing standard 允许在下列场景中使用跨站点 HTTP 请求:
- 前文提到的由 XMLHttpRequest 或 Fetch 发起的跨源 HTTP 请求。 
- Web 字体 (CSS 中通过 @font-face 使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。 
- 使用 - drawImage将- Images/video画面绘制到- canvas
怎么使用 CORS?
CORS的使用的关键在服务端,浏览器发送请求,服务端接收到客户端请求做一些判断(请求方是否在自己的“白名单”里?),如果没问题就返回数据,否则拒绝。
浏览器将 CORS 请求分成两类:
- 简单请求( - simple request)
- 非简单请求( - not-so-simple request)
简单请求(simple request)
只要同时满足以下两大条件,就是简单请求:
- 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
下面来看看这两种请求,CORS 是怎么处理?
基本流程
对于简单请求,浏览器直接发出 CORS 请求,在头信息之中,增加一个Origin字段。
下面是来看一个例子,浏览器判断这次跨源AJAX请求是简单请求,就会自动在头信息之中,添加一个Origin字段。
接下来服务端收到浏览器请求,首先检测请求报头的 Origin 是否在自己的许可范围内,
如果确实是许可的域,会响应的时候,在响应头额外增加如下字段:
- Access-Control-Allow-Origin(必选) :这个字段用来告知浏览器,服务端能够接受的发送 AJAX 请求的域,因为此次请求得到许可,所以这里返回与先前请求报头中- Origin匹配的- http://api.infoq.cn。当然,也可以返回 ,表示接受任何域的 AJAX 请求( 是通配的意思)。
- Access-Control-Allow-Credentials(可选):告知浏览器,是否允许浏览器发送请求的时候携带- Cookie,- true表示允许,- false表示禁止,出于安全问题考虑,- CORS默认不允许跨域 AJAX 请求携带- Cookie。
- Access-Control-Expose-Headers(可选):该字段为可选字段。- CORS请求时,- XMLHttpRequest对象的- getResponseHeader()方法只能拿到 6 个基本字段:- Cache-Control、- Content-Language、- Content-Type、- Expires、- Last-Modified、- Pragma。如果想拿到其他字段,就必须在- Access-Control-Expose-Headers里面指定。
如果不是许可的域,这时候不会返回 Access-Control-Allow-Origin 这个响应头,而浏览器会捕获这次错误,如下图所示
 
 虽然禁止跨域
AJAX请求携带Cookie是为了安全考虑,但由于它在身份验证中的重要性,我们有时候还是得携带Cookie的。 具体方法是:
如果需要安全携带 Cookie,需要另外一个属性:withCredentials。
CORS请求默认不发送Cookie和HTTP认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。
- 客户端配置 - withCredentials属性:
- 服务端配置 - Access-Control-Allow-Credential为 true,配置- Access-Control-Allow-Origin为指定的域(而不是 * ),
如果省略withCredentials设置,有的浏览器还是会一起发送 Cookie。这时,可以显式关闭withCredentials。
另外需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
非简单请求(not-so-simple request)
非简单请求包括两次请求,第一次请求是 preflight request,也就是预检/查询请求,这次请求试探性地“询问”服务端,自己打算进行的非简单请求是否合法 —— 不管是否合法,服务端都会通过某种方式通知客户端,客户端基于这个结果,判断是否进行第二次真正的请求。
预检请求
首先是客户端的角度,发送请求时浏览器检测到这是一个非简单请求,所以事先向服务端发送一个预检请求:
- 注意,这里这个预检请求的类型是 - OPTIONS。
- 像之前的简单请求一样,这里浏览器会追加一个 - Origin,表示请求代码所在的源
- 前面我们说过,非简单请求会多出额外的请求头字段,这里多出来的就是 - Access-Control-Request-Method和- Access-Control-Request-Headers,这其实是告诉服务端,“我待会要进行的真正请求,类型是这里- Access-Control-Request-Headers指定的类型,然后自定义请求头是这里- Access-Control-Request-Headers指定的值,你看看行不行,给我个回应“。
预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
- Access-Control-Allow-Origin:这里和之前一样,可以是- https://www.infoq.cn或者- *,也就是告诉客户端,“我给你的域下了许可证“
- Access-Control-Allow-Methods:这里告诉客户端,服务端允许的跨域- AJAX请求的类型,”虽然你刚才告诉我你准备进行的是- PUT请求,不过你要进行- GET或者- POST请求,我也是允许的“
- Access-Control-Allow-Headers:如果浏览器请求包括- Access-Control-Request-Headers字段,则- Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
- Access-Control-Max-Age: 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒(即 20 天),在此期间,不用发出另一条预检请求。
浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是"预检"请求之后,浏览器的正常 CORS 请求。
上面头信息的Origin字段是浏览器自动添加的。
下面是服务器正常的回应。
上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。
和 JSONP 的差异?
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
版权声明: 本文为 InfoQ 作者【devpoint】的原创文章。
原文链接:【http://xie.infoq.cn/article/6ba7e73bc8b6d758cbafc8f96】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。












 
    
评论