1. 程式人生 > >簡單跨域請求和帶預檢的跨域請求

簡單跨域請求和帶預檢的跨域請求

受瀏覽器的同源策略限制,JavaSript只能請求本域內的資源。跨域資源共享(Cross-Origin Resource Sharing, CORS)是為解決Ajax技術難實現跨域問題而提出的一個規範,這個規範試著從根本上解決安全的跨域資源共享問題。在此之前,解決此類問題的途徑往往是伺服器代理、JSONP等,治標不治本。目前基本所有瀏覽器都已經支援該規範。

一個域是由schema、host、port三者共同組成,與路徑無關。所謂跨域,是指在http://example-foo.com/域上通過XMLHttpRequest物件呼叫http://example-bar.com/域上的資源。CORS約定伺服器端和瀏覽器在HTTP協議之上,通過一些額外HTTP頭部資訊,進行跨域資源共享的協商。伺服器端和瀏覽器都必需遵循規範中的要求。

CORS把HTTP請求分成兩類,不同類別按不同的策略進行跨域資源共享協商。

1. 簡單跨域請求。
當HTTP請求出現以下兩種情況時,瀏覽器認為是簡單跨域請求:

1). 請求方法是GET、HEAD或者POST,並且當請求方法是POST時,Content-Type必須是application/x-www-form-urlencoded, multipart/form-data或著text/plain中的一個值。
2). 請求中沒有自定義HTTP頭部。

對於簡單跨域請求,瀏覽器要做的就是在HTTP請求中新增Origin Header,將JavaScript指令碼所在域填充進去,向其他域的伺服器請求資源。伺服器端收到一個簡單跨域請求後,根據資源許可權配置,在響應頭中新增Access-Control-Allow-Origin Header。瀏覽器收到響應後,檢視Access-Control-Allow-Origin Header,如果當前域已經得到授權,則將結果返回給

JavaScript。否則瀏覽器忽略此次響應。

2. 帶預檢(Preflighted)的跨域請求。
當HTTP請求出現以下兩種情況時,瀏覽器認為是帶預檢(Preflighted)的跨域請求:

1). 除GET、HEAD和POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他HTTP方法。
2). 請求中出現自定義HTTP頭部。

帶預檢(Preflighted)的跨域請求需要瀏覽器在傳送真實HTTP請求之前先發送一個OPTIONS的預檢請求,檢測伺服器端是否支援真實請求進行跨域資源訪問,真實請求的資訊在OPTIONS請求中通過Access-Control-Request-Method Header和Access-Control-Request-Headers Header描述,此外與簡單跨域請求一樣,瀏覽器也會新增Origin Header。伺服器端接到預檢請求後,根據資源許可權配置,在響應頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請求的域、請求方法和請求頭。此外,伺服器端還可以加入Access-Control-Max-Age Header,允許瀏覽器在指定時間內,無需再發送預檢請求進行協商,直接用本次協商結果即可。瀏覽器根據OPTIONS請求返回的結果來決定是否繼續傳送真實的請求進行跨域資源訪問。這個過程對真實請求的呼叫者來說是透明的。

XMLHttpRequest支援通過withCredentials屬性實現在跨域請求攜帶身份資訊(Credential,例如Cookie或者HTTP認證資訊)。瀏覽器將攜帶Cookie Header的請求傳送到伺服器端後,如果伺服器沒有響應Access-Control-Allow-Credentials Header,那麼瀏覽器會忽略掉這次響應。

這裡討論的HTTP請求是指由Ajax XMLHttpRequest物件發起的,所有的CORS HTTP請求頭都可由瀏覽器填充,無需在XMLHttpRequest物件中設定。以下是CORS協議規定的HTTP頭,用來進行瀏覽器發起跨域資源請求時進行協商:
1. Origin。HTTP請求頭,任何涉及CORS的請求都必需攜帶。
2. Access-Control-Request-Method。HTTP請求頭,在帶預檢(Preflighted)的跨域請求中用來表示真實請求的方法。
3. Access-Control-Request-Headers。HTTP請求頭,在帶預檢(Preflighted)的跨域請求中用來表示真實請求的自定義Header列表。
4. Access-Control-Allow-Origin。HTTP響應頭,指定伺服器端允許進行跨域資源訪問的來源域。可以用萬用字元*表示允許任何域的JavaScript訪問資源,但是在響應一個攜帶身份資訊(Credential)的HTTP請求時,Access-Control-Allow-Origin必需指定具體的域,不能用萬用字元。
5. Access-Control-Allow-Methods。HTTP響應頭,指定伺服器允許進行跨域資源訪問的請求方法列表,一般用在響應預檢請求上。
6. Access-Control-Allow-Headers。HTTP響應頭,指定伺服器允許進行跨域資源訪問的請求頭列表,一般用在響應預檢請求上。
7. Access-Control-Max-Age。HTTP響應頭,用在響應預檢請求上,表示本次預檢響應的有效時間。在此時間內,瀏覽器都可以根據此次協商結果決定是否有必要直接傳送真實請求,而無需再次傳送預檢請求。

8. Access-Control-Allow-Credentials。HTTP響應頭,凡是瀏覽器請求中攜帶了身份資訊,而響應頭中沒有返回Access-Control-Allow-Credentials: true的,瀏覽器都會忽略此次響應。

總結:只要是帶自定義header的跨域請求,在傳送真實請求前都會先發送OPTIONS請求,瀏覽器根據OPTIONS請求返回的結果來決定是否繼續傳送真實的請求進行跨域資源訪問。所以複雜請求肯定會兩次請求服務端。

js 端的ajax請求:

[javascript] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
  1. $.ajax({  
  2.        url: "http://test.com",  
  3.        dataType: 'json',  
  4.        type: 'GET',  
  5.        beforeSend: function (xhr) {  
  6.            xhr.setRequestHeader("Test""testheadervalue");  
  7.        },  
  8.        async: false,  
  9.        cache: false,  
  10.        //contentType: 'application/x-www-form-urlencoded',
  11.        success: function (sResponse) {  
  12.        }  
  13.    });  

服務端的action

  1. //允許跨域訪問
  2.            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin""*");  
  3.            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods""POST, GET, OPTIONS,DELETE,PUT");  
  4.            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers""Test");  

注:帶預檢(Preflighted)的跨域請求的第一次請求,header中不會帶自定義的header頭資訊。