1. 程式人生 > >跨域資源共享CORS

跨域資源共享CORS

1. 同源政策

1.1 含義

  • 1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。
  • 最初,它的含義是指,A網頁設定的 Cookie,B網頁不能開啟,除非這兩個網頁”同源”。所謂”同源”指的是”三個相同”。
協議相同
域名相同
埠相同

舉例來說,http://www.example.com/dir/page.html這個網址,協議是http://,域名是www.example.com,埠是80(預設埠可以省略)。它的同源情況如下。

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html
:不同源(域名不同) http://v2.www.example.com/dir/other.html:不同源(域名不同) http://www.example.com:81/dir/other.html:不同源(埠不同)

2. 跨域解決方案CORS

2.1 定義:

  • CORS 是一個 W3C 標準,全稱是”跨域資源共享”(Cross-origin resource sharing)。CORS 需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE 瀏覽器不能低於 IE10。
  • 它允許瀏覽器向跨源伺服器,發出 XMLHttpRequest 請求,從而克服了 AJAX 只能同源使用的限制。整個 CORS 通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS 通訊與同源的 AJAX 通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現 AJAX 請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。因此,實現 CORS 通訊的關鍵是伺服器。只要伺服器實現了 CORS 介面,就可以跨源通訊。

2.2 兩種跨域請求

2.2.1 簡單請求

  • 同時滿足以下條件,那麼就是簡單請求。
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) 如果不滿足上述條件任何一個,那麼它就是預檢請求 (非簡單請求)。

瀏覽器發現自己傳送的是簡單跨域請求,則會只發送一次HTTP請求。相較於同源請求,CORS簡單請求會在頭資訊中額外增加一個Origin欄位。

下圖是一個簡單跨域請求例子:瀏覽器發現本次請求是跨域請求,就會自動在請求頭資訊中增加Origin欄位(依賴於瀏覽器機制/或者JS直譯器的實現) 簡單跨域

  • 請求和響應內容如下: 假定是從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欄位,就知道出錯了,從而丟擲一個錯誤,被XMLHttpRequestonerror回撥函式捕獲。

2.2.2 預檢請求

  • 不滿足簡單請求條件之一的即是非簡單請求。非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。
  • 「預檢(preflight)」請求會先用HTTP 的OPTIONS 方法請求另一個域名資源,確認後續實際(actual)請求能否可安全送出。由於跨域請求可能會攜帶使用者的資訊,所以要先進行預檢請求。

下圖是一個預檢請求例子: 這裡寫圖片描述

請求和響應內容如下: 假定是從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
  • 先看請求,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小時也就是一天。

等到預檢請求完成後,瀏覽器才會傳送真正的響應:

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]

問題: 1、若簡單跨域請求校驗失敗,APIGW應如何回覆? 2、若預檢跨域請求校驗失敗,APIGW應如何回覆? 3、目前瀏覽器並不支援預檢請求的重定向,如果發生了預檢請求的重定向,則瀏覽器會大概率報錯

3. 跨域請求實現

3.1 原始CORS

1)首先修改 cart-web 的 CartController.java 的 addGoodsToCartList 方法,新增下面兩句程式碼

// 可以訪問的域, 此方法不需要訪問 cookie
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9105"); 
// 如果要操作 cookie, 則必須加上這句話
response.setHeader("Access-Control-Allow-Credentials", "true");

Access-Control-Allow-Origin : - Access-Control-Allow-Origin 是 HTML5 中定義的一種解決資源跨域的策略。他是通過伺服器端返回帶有 Access-Control-Allow-Origin 標識的 Response header,用來解決資源的跨域許可權問題。 - 使用方法,在 response 新增 Access-Control-Allow-Origin,例如 : Access-Control-Allow-Origin:www.google.com , 也可以設定為*表示該資源誰都可以用

2)修改 pyg-item-web 的 itemController.js


    //新增商品到購物車
    $scope.addGoodsToCartList=function(){
        //這是一個跨域的地址
        var url='http://localhost:8086/cart/addGoodsToCartList/'+$scope.sku.id+'/'+$scope.num + '-';
        // {'withCredentials':true} : 是否使用憑證, 需要訪問 cookie 時加上
        $http.get(url,{'withCredentials':true}).success(
            function(response){
                if(response.success){
                    location.href="http://localhost:8086/cart.html";
                }else{
                    alert(response.message);
                }
            }
        );
    } 

CORS 請求預設不傳送 Cookie 和 HTTP 認證資訊。如果要把 Cookie 發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials 欄位。另一方面,開發者必須在 AJAX 請求中開啟withCredentials 屬性。否則,即使伺服器同意傳送 Cookie,瀏覽器也不會發送。或者,伺服器要求設定 Cookie,瀏覽器也不會處理。

3.2 SpringMVC 跨域註解

  • springMVC 的版本在 4.2 或以上版本,可以使用註解實現跨域, 我們只需要在需要跨域的方法上添加註解@CrossOrigin 即可
// allowCredentials="true" 是否使用憑證, 需要訪問cookie時設定, 可以預設,預設值時true
@CrossOrigin(origins="http://localhost:9105",allowCredentials="true")

4. 錯誤資訊

1. 無法跨域

無法跨域

2. 重定向不支援跨域請求

重定向不支援跨域請求

參考文獻