跨域資源共享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
欄位,就知道出錯了,從而丟擲一個錯誤,被XMLHttpRequest
的onerror
回撥函式捕獲。
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")