跨域資源共享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/

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