跨域资源共享CORS的那些事(二)

跨域资源共享CORS的那些事(二)

最近在为高性能开源API网关apisix写跨域插件,发现该功能对协议要求要比较熟悉,借此机会重新复习下跨域协议,以及简要写下跨域功能的设计

定义

跨来源资源共享(Cross-Origin Resource Sharing(CORS))是一种使用额外HTTP标头来让目前浏览网站的user agent能获得访问不同来源(网域)服务器特定资源之权限的机制。当user agent请求一个不是目前文件来源——来自于不同网域(domain)、通信协定(protocol)或通信端口(port)的资源时,会建立一个跨来源HTTP请求(cross-origin HTTP request)。

基于安全性考虑,浏览器和WebView发出的HTTP请求会有限制。例如,XMLHttpRequest及Fetch皆遵守同源政策(same-origin policy)。这代表网络应用程序所使用的这些API只能请求来自和应用程序相同网域的HTTP资源,除非使用了CORS标头。

哪些请求会使用到CORS?

  1. 使用XMLHttpRequest或Fetch API进行跨站请求,如前所述。
  2. 网页字体(跨网域CSS的@font-face的字体用途),所以服务器可以部属TrueType字体并限制仅让被许可的网站进行跨站加载使用。
    WebGL纹理。
  3. 以drawImage绘制到Canvas画布上的图形/视频之影格。
  4. CSS样式表(让CSSOM存取)。
  5. 指令码(for unmuted exceptions)

跨域请求详解

CORS需要浏览器和服务器同时支持。当前桌面和移动浏览器对CORS的支持情况如下:

a、浏览器端支持情况

桌面浏览器:

浏览器 Chrome Edge FireFox IE Opera Safari
支持CORS最低版本 4 12 3.5 10 12 4
  • IE8 和 IE9 通过 XDomainRequest 插件支持CORS,IE10 开始则完全正常支持CORS。
  • Firefox 3.5 支持跨域 XMLHttpRequests 与 Web Fonts,较旧版本上某些请求会有限制。 Firefox 7 支持 WebGL 纹理的跨域 HTTP 请求,而 Firefox 9 新增支持使用 drawImage 方法,将图形绘制于 canvas 中。

移动浏览器:

浏览器 Android webview Chrome for Android Edge mobile Firefox for Android IE mobile Opera Android iOS Safari
支持CORS最低版本 2.1 ALL ALL 4 ALL 12 3.2

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源通信没有差别,代码完全一样。浏览器一旦发现HTTP请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求(复杂请求),但用户不会感知。

支持CORS的主要改动点在server端

b、两种跨域请求

浏览器将CORS请求分成两类:简单请求(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
(3)没有事件监听器被注册到任何用来发出请求的 XMLHttpRequestUpload 上(经由 XMLHttpRequest.upload 方法取得)上。
(4)请求中没有 ReadableStream 类型的内容被用于上传。

PS:虽然这些都是网页目前已经可以送出的跨站请求,除非后端服务器回复正确CORS标志,否则不会有内容传回来,因此不允许跨域请求的网站无须担心会受到新的HTTP 存取控制的影响。

通常情况下主要涉及条件(1)和条件(2)

如果不满足上述条件任何一个,那么它就是预检请求。

c、简单请求

浏览器发现自己发送的是简单跨域请求,则会只发送一次HTTP请求。相较于同源请求,CORS简单请求会在头信息中额外增加一个Origin字段。

下图是一个简单跨域请求例子:浏览器发现本次请求是跨域请求,就会自动在请求头信息中增加Origin字段(依赖于浏览器机制/或者JS解释器的实现)
image

请求和响应内容如下:

假定是从apigw.qcloud.com去请求qcloud.com的资源

请求
GET /resources/public-data/ HTTP/1.1
Host: qcloud.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://apigw.qcloud.com


响应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

HTTP请求中的Origin字段表示该请求是来自于http://apigw.qcloud.com的请求

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: application/xml

本次请求示例中响应是携带了Access-Control-Allow-Origin: *,或者是Access-Control-Allow-Origin: http://apigw.qcloud.com

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

d、预检请求

不满足简单请求条件之一的即是非简单请求。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

「预检(preflighted)」请求会先用HTTP 的OPTIONS 方法请求另一个域名资源,确认后续实际(actual)请求能否可安全送出。由于跨域请求可能会携带使用者的信息,所以要先进行预检请求。

下图是一个预检请求例子:

image

请求和响应内容如下:

假定是从apigw.qcloud.com去请求qcloud.com的资源

第一次是预检请求/响应:

OPTIONS /resources/post-here/ HTTP/1.1
Host: qcloud.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://apigw.qcloud.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


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://apigw.qcloud.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

等到预检请求完成后,浏览器才会发送真正的响应:

POST /resources/post-here/ HTTP/1.1
Host: qcloud.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Content-Length: 55
Origin: http://apigw.qcloud.com
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://apigw.qcloud.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

先看请求,Access-Control-Request-Method告诉服务器发的请求是POST请求,Access-Control-Request-Headers通知自己带有X-PINGOTHER自定义header

再看响应,Access-Control-Allow-Origin这个与前面类似,Access-Control-Allow-Methods这里说明支持POST/GET/OPTIONS方法,Access-Control-Allow-Headers这里说明允许X-PINGOTHER自定义header,Access-Control-Max-Age用来指定本次预检请求的有效时间,86400是24小时也就是一天。

问题:1、若简单跨域请求校验失败,APIGW应如何回复?
2、若预检跨域请求校验失败,APIGW应如何回复?
3、目前浏览器并不支持预检请求的重定向,如果发生了预检请求的重定向,则浏览器会大概率报错

一旦服务器通过了"预检"请求,在Access-Control-Max-Age指定的时间内,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

e、HTTP跨域请求标识

Origin

Origin 字段表示了跨域请求的来源或者预检请求的来源。在任何跨域请求中,一定要携带Origin字段

Origin: <origin>

Access-Control-Request-Method(仅在预检请求中)

Access-Control-Request-Method 是用在预检请求中,告诉后端server实际请求用的HTTP方法

Access-Control-Request-Method: <method>

Access-Control-Request-Headers(仅在预检请求中)

Access-Control-Request-Headers标识用于预检请求中,它会告诉后端server自己所携带的自定义header字段有哪些

Access-Control-Request-Headers: <field-name>[, <field-name>]*

f、HTTP跨域响应标识

Access-Control-Allow-Origin

跨域响应会携带该字段,若服务器允许所有uri来访问自己的资源,那么则该字段为*;若要允许http://www.qq.com访问该资源,则为Access-Control-Allow-Origin: http://www.qq.com

Access-Control-Allow-Origin: <origin> | *

Access-Control-Expose-Headers

Access-Control-Expose-Headers表示服务器允许浏览器从响应中解析哪些header字段

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

表示服务器允许浏览器从响应中解析X-My-Custom-Header, X-Another-Custom-Header字段

Access-Control-Max-Age

Access-Control-Max-Age表示预检请求结果请求成功后,多长时间内非简单请求可以不需要再发预检请求,可以继续直接使用跨域请求请求资源

Access-Control-Max-Age: <delta-seconds>

Access-Control-Allow-Credentials

这里下次补充。

Access-Control-Allow-Credentials: true

Access-Control-Allow-Methods(仅在预检请求响应中)

Access-Control-Allow-Methods表示服务器访问操作该资源允许哪些方法

Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Allow-Headers(仅在预检请求响应)

Access-Control-Allow-Headers表示在访问这个域资源的时候,预检请求响应中哪些header字段可以在跨域请求中使用

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

参考文档

https://www.w3.org/TR/cors/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章