CORS


前面的話

  通過XHR實現Ajax通信的一個主要限制,來源於跨域安全策略。默認情況下,XHR對象只能訪問與包含它的頁面位於同一個域中的資源。這種安全策略可以預防某些惡意行為。但是,實現合理的跨域請求對開發某些瀏覽器應用程序也是至關重要的。CORS(Cross-Origin Resource Sharing)跨源資源共享是W3C的一個工作草案,定義了在必須訪問跨源資源時,瀏覽器與服務器應該如何溝通。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。本文將詳細介紹CORS的內容

 

簡單請求

  瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-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)

  凡是不同時滿足上面兩個條件,就屬於非簡單請求

  CORS背后的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗

  對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,需要給它附加一個額外的origin頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應

  瀏覽器如果發現跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin字段

Accept:*/*
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:0
Host:www.webhuochai.com
Origin:http://127.0.0.1
Referer:http://127.0.0.1/cors.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36

  上面的頭信息中,Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求

  如果服務器認為這個請求可以接受,就在Access-Control-Allow-Origin頭部中回發相同的源信息(如果是公共資源,可以回發" *" )

Request URL:https://www.webhuochai.com/test/iecors.php
Request Method:POST
Status Code:200 OK
Remote Address:218.247.93.253:443
Referrer Policy:no-referrer-when-downgrade

  如果Origin指定的源,不在許可范圍內,服務器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。但是,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200

  [注意]請求和響應都不包含cookie信息

 

原生支持

  標准瀏覽器都通過XMLHttpRequest對象實現了對CORS的原生支持。在嘗試打開不同來源的資源時,無需額外編寫代碼就可以觸發這個行為。要請求位於另一個域中的資源,使用標准的XHR對象並在open()方法中傳入絕對URL即可

  [注意]IE9-瀏覽器不支持

<input id="btn" type="button" value="跨域請求">
<div id="result"></div>
<script>
btn.onclick = function(){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300)|| xhr.status == 304){
                result.innerHTML = xhr.responseText;
            }else{
                alert("Request was unsuccessful: " + xhr.status);
            }    
        }
    };
    xhr.open("post", "https://www.webhuochai.com/test/iecors.php", true);
    xhr.send(null);    
}
</script>    

  CORS主要需要在后端進行設置,以PHP為例

  通過設置header()方法,“*”號表示允許任何域向服務端提交請求:

header( " Access-Control-Allow-Origin: * " );

  也可以設置指定的域名,如域名"https://www.webhuochai.com",那么就允許來自這個域名的請求

header( " Access-Control-Allow-Origin: https://www.webhuochai.com" );

  通過跨域XHR對象可以訪問status和statusText屬性,而且還支持同步請求。跨域XHR對象也有一些限制,但為了安全這些限制是必需的

  1、不能使用setRequestHeader()設置自定義頭部

  2、不能發送和接收cookie

  3、調用getAllResponseHeaders()方法總會返回空字符串

  由於無論同源請求還是跨源請求都使用相同的接口,因此對於本地資源,最好使用相對URL,在訪問遠程資源時再使用絕對URL。這樣做能消除歧義,避免出現限制訪問頭部或本地cookie信息等問題

 

IE實現

  微軟引入了XDR(XDomainRquest)類型。這個對象與XHR類似,但能實現安全可靠的跨域通信。XDR對象的安全機制部分實現了W3C的CORS規范

  [注意]IE11瀏覽器不支持

  以下是XDR與XHR的一些不同之處

  1、cookie不會隨請求發送,也不會隨響應返回

  2、只能設置請求頭部信息中的Content-Type字段

  3、不能訪問響應頭部信息

  4、只支持GET和POST請求

  這些變化使CSRF(Cross-Site Request Forgery)跨站點請求偽造和XSS(Cross-Site Scripting)跨站點腳本的問題得到了緩解。被請求的資源可以根據它認為合適的任意數據(用戶代理、來源頁面等)來決定是否設置Access-Control-Allow-Origin頭部。作為請求的一部分,Origin頭部的值表示請求的來源域,以便遠程資源明確地識別XDR請求

  XDR對象的使用方法與XHR對象非常相似。也是創建一個XDomainRequest的實例,調用open()方法,再調用send()方法。但與XHR對象的open()方法不同,XDR對象的open()方法只接收兩個參數:請求的類型和URL

  所有XDR請求都是異步執行的,不能用它來創建同步請求。請求返回之后,會觸發load事件,響應的數據也會保存在responseText屬性中,如下所示

<input id="btn" type="button" value="跨域請求">
<div id="result"></div>
<script>
btn.onclick = function(){
    var xdr = new XDomainRequest();
    xdr.onload = function(){
        result.innerHTML = xdr.responseText;
    };
    xdr.open("get", "https://www.webhuochai.com/test/iecors.php");
    xdr.send(null);
}
</script>

  [注意]在IE瀏覽器中,不支持http與https之間,即不同協議間的跨域請求,會提示SCRIPT5: 拒絕訪問

  在接收到響應后,只能訪問響應的原始文本;沒有辦法確定響應的狀態代碼。而且,只要響應有效就會觸發load事件,如果失敗(包括響應中缺少Access-Control-Allow-Origin頭部)就會觸發error事件。遺憾的是,除了錯誤本身之外,沒有其他信息可用,因此唯一能夠確定的就只有請求未成功了

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
}
xdr.open("get", "https://www.webhuochai.com/test/iecors.php");
xdr.send(null);

  鑒於導致XDR請求失敗的因素很多,因此建議不要忘記通過onerror事件處理程序來捕獲該事件;否則,即使請求失敗也不會有任何提示

  在請求返回前調用abort()方法可以終止請求

xdr.abort(); //終止請求

  與XHR一樣,XDR對象也支持timeout屬性以及ontimeout事件處理程序

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
    alert("Request took too long.");
};
xdr.open("get", "https://www.webhuochai.com/test/iecors.php");
xdr.send(null);

  這個例子會在運行1秒鍾后超時,並隨即調用ontimeout事件處理程序

  為支持POST請求,XDR對象提供了contentType屬性,用來表示發送數據的格式

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("post", "https://www.webhuochai.com/test/iecors.php");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");

  這個屬性是通過XDR對象影響頭部信息的唯一方式

 

Preflight

  CORS通過一種叫做Preflighted Requests(預檢請求)的透明服務器驗證機制支持開發人員使用自定義的頭部、GET或POST之外的方法,以及不同類型的主體內容

  [注意]IE10-瀏覽器不支持

  在使用下列高級選項來發送請求時,就會向服務器發送一個Preflight請求。這種請求使用OPTIONS方法,發送下列頭部

  1、Origin:與簡單的請求相同

  2、Access-Control-Request-Method:請求自身使用的方法

  3、Access-Control-Request-Headers:(可選)自定義的頭部信息,多個頭部以逗號分隔

  以下是一個帶有自定義頭部NCZ的使用POST方法發送的請求

Origin: http://www.nczonline.net
Access-Control-Request-MeChod: POST
Access-Control-Request-Headers: NCZ

  發送這個請求后,服務器可以決定是否允許這種類型的請求。服務器通過在響應中發送如下頭部與瀏覽器進行溝通

  1、Access-Control-Allow-Origin:與簡單的請求相同

  2、Access-Control-Allow-Methods:允許的方法,多個方法以逗號分隔

  3、Access-Control-Allow-Headers:允許的頭部,多個頭部以逗號分隔

  4、Access-Control-Max-Age:應該將這個Preflight請求緩存多長時間(以秒表示)

Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

  Preflight請求結束后,結果將按照響應中指定的時間緩存起來。而為此付出的代價只是第一次發送這種請求時會多一次HTTP請求

 

帶憑據請求

  默認情況下,跨源請求不提供憑據(cookie、HTTP認證及客戶端SSL證明等)。通過將withCredentials屬性設置為true,可以指定某個請求應該發送憑據

  [注意]IE10-瀏覽器不支持

  如果服務器接受帶憑據的請求,會用下面的HTTP頭部來響應

Access-Control-Allow-Credentials: true

  開發者必須在AJAX請求中打開withCredentials屬性

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

  否則,即使服務器同意發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理

  但是,如果省略withCredentials設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials

xhr.withCredentials = false;

  如果發送的是帶憑據的請求,但服務器的響應中沒有包含這個頭部,那么瀏覽器就不會把響應交給javascript(於是,responseText中將是空字符串,status的值為0,而且會調用onerror()事件處理程序)。另外,服務器還可以在Preflight響應中發送這個HTTP頭部,表示允許源發送帶憑據的請求

  需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取服務器域名下的Cookie

 

跨瀏覽器

  即使瀏覽器對CORS的支持程度並不都一樣,但所有瀏覽器都支持簡單的(非Preflight和不帶憑據的)請求,因此有必要實現一個跨瀏覽器的方案。檢測XHR是否支持CORS的最簡單方式,就是檢査是否存在withCredentials屬性。再結合檢測XDomainRequest對象是否存在,就可以兼顧所有瀏覽器了

function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    //標准瀏覽器
    if("withCredentials" in xhr){
        xhr.open(method, url, true);
    //IE10-瀏覽器
    }else if(typeof XDomainRequest != "undefined"){
        xhr = new XDomainRequest();
        xhr.open(method, url); 
    } 
    return xhr;
}

  非IE瀏覽器中的XMLHttpRequest對象與IE中的XDomainRequest對象類似,都提供了夠用的接口,因此以上模式還是相當有用的。這兩個對象共同的屬性/方法如下

  1、abort():用於停止正在進行的請求

  2、onerror:用於替代 onreadystatechange 檢測錯誤

  3、onload:用於替代 onreadystatechange 檢測成功

  4、responseText:用於取得響應內容

  5、send():用於發送請求


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM