WEB知識: 同源策略介紹以及規避方法

所謂同源策略(Same origin policy),其實就是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。

何謂同源策略:

同源即是指域名、協議與端口相同,不同源的客戶端腳本(JavaScript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。詳細情況見下圖:

同源策略的目的:

它的目的是爲了保證用戶信息的安全,防止惡意的網站竊取數據。

試想一下,如果你登錄了某個人空間網站,該網站保存了你登錄狀態的cookie,而一個惡意網站可以隨意讀取該網站的cookie,那麼事情就大條了,如果你沒有退出登錄狀態,調皮一點的黑客可能會冒充你去發一張摳腳大漢賣萌的圖片,嚇壞你的朋友,而別有用心的黑客可能就會竊取你的個人信息去做一些違法亂紀的事情,Web世界將毫無安全可言!

同源策略具體限制那些行爲:

總體來看。對於非同源情況,目前有如下嚴格限制:

a.Cookie、LocalStorage 和 IndexDB 無法讀取

b.DOM 無法獲得

c.AJAX 請求不能發送

雖然同源策略是爲了用戶安全考慮,但是用戶的某些合理的請求卻也遭到了限制,帶來了一些不方便之處,所以,怎麼樣才能在合理情況下規避同源策略呢?

規避方法:

跨域資源共享CORS

CORS含義:

CORS是一個W3C標準,它允許瀏覽器跨越服務器,發出AJAX請求,從而克服AJAX的同源限制。

CORS與用戶無關,瀏覽器會自動檢測AJAX請求,若該請求跨域,則自動附加一些必要的請求頭,無需用戶多加操作,它實現的關鍵點在於服務器,只要服務器實現了CORS接口,那麼就可以實現CORS跨源通信。

CORS請求分類:

對於一個CORS的HTTP請求來說,根據請求方法和HTTP請求頭的不同,瀏覽器將其分爲「簡單請求」和「非簡單請求」兩種方式。

簡單請求:滿足以下兩個條件的即爲簡單請求

a、請求方法爲HEAD、GET、POST中的一種;

b、HTTP請求頭不超過這些字段Accept、Accept-Language、Content-LanguageLast-Event-ID、Content-Type(只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain)

非簡單請求:不同時滿足上面兩個條件的請求即爲非簡單請求

CORS簡單請求實現方法:

a、用戶發起跨源AJAX請求

b、瀏覽器檢測該請求。若該請求爲簡單請求,則在請求頭信息內添加如下字段併發送:

Origin: 協議+域名+端口
Origin字段:指明本次請求來自哪裏

c、服務器接收到信息,檢測該Origin字段是否在許可範圍內

d、若該字段在許可範圍內,則在響應頭信息內添加如下字段後返回:

Access-Control-Allow-Origin: 協議+域名+端口
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers:字段名稱
Access-Control-Allow-Origin:(必須)該字段值有兩種情況,與請求頭Origin字段相同,表示同意該域名跨域訪問,或者爲一個星號「*」,表示接收任意域名訪問。

Access-Control-Allow-Credentials:(可選)該字段值爲一個布爾值,默認爲true,表示服務器允許瀏覽器將cookie與請求一併發送給服務器。該字段有且僅有這一個值,若服務器不同意發送Cookie給自己,則刪除該字段即可。另外,該字段必須搭配「withCredentials」屬性使用。「withCredentials」屬性默認爲false,表示瀏覽器發送請求時不發送cookie憑據。若要發送,需設置爲true,且「Access-Control-Allow-Origin」字段的值不可以爲「*」,必須指定明確的、與請求網頁一致的域名。總而言之,「withCredentials」屬性告訴AJAX是否發送cookie憑據,「Access-Control-Allow-Credentials」告訴服務器是否接受cookie憑據,發送時,二者缺一不可。

Access-Control-Expose-Headers:(可選)該字段值爲響應頭的字段名稱,默認情況下,XMLHttpRequest對象的getResponseHeader()方法只能獲得6個基本字段。該值可以指定getResponseHeader()方法可以獲得的其他字段的名稱。

瀏覽器檢測響應頭有包含「Access-Control-Allow-Origin」字段,證明跨域成功,獲得信息。

e、若該字段不在許可範圍內,則不添加任何信息,正常返回HTTP響應

瀏覽器檢測響應頭沒有包含「Access-Control-Allow-Origin」字段,證明跨域失敗,拋出錯誤,該錯誤可被AJAX的onerror回調函數捕獲。

CORS非簡單請求實現方法:

a、用戶發起跨源AJAX請求

b、瀏覽器檢測該請求,若該請求爲非簡單請求,則瀏覽器在正式通信前發起一次「預檢」請求

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
預檢請求使用「options」方法,表示該請求爲詢問請求
一個關鍵字段「origin」:表示正式通信請求來自哪裏

「Access-Control-Request-Method」:表示正式通信請求需要用到的請求方法

「Access-Control-Request-Headers」:表示正式通信請求需要使用的額外的頭信息

c、服務器檢測該預檢請求提供的三個字段

d、若同意跨源通信,則在響應頭信息內添加如下字段後返回:

Access-Control-Allow-Origin:協議+域名+端口
Access-Control-Allow-Methods:請求方式
Access-Control-Allow-Headers: 額外的請求頭
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 預檢請求有效期
Access-Control-Allow-Origin:(必須)該字段值有兩種情況,與請求頭Origin字段相同,表示同意該域名跨域訪問,或者爲一個星號「*」,表示接收任意域名訪問。

Access-Control-Allow-Methods:(必須)該字段值爲服務器允許的所有跨域方法,不限於預檢中請求的字段,避免了請求方法不同而造成的多次預檢。

Access-Control-Allow-Headers:(分情況)若請求頭包含「Access-Control-Request-Headers」字段,則爲必須,若請求頭未包含「Access-Control-Request-Headers」字段則不需要。該字段指明服務器支持的所有頭信息字段。

Access-Control-Allow-Credentials:(可選)與簡單請求含義相同,具體見上文。

Access-Control-Max-Age:(可選)該字段表明了預檢的有效期,在有效期內相同請求不必再次預檢。

瀏覽器檢測響應頭有包含「Access-Control-Allow-Origin」字段,證明服務器同意該跨域通信,執行f步驟。

e、若不同意跨源通信,則不添加任何信息,正常返回HTTP響應

瀏覽器檢測響應頭沒有包含「Access-Control-Allow-Origin」字段,證明服務器不同意該跨域通信,拋出錯誤,該錯誤可被AJAX的onerror回調函數捕獲。

f、d步驟完成,證明同意跨源通信,則瀏覽器開始發送正式請求,具體方法與簡單請求相同。


JSONP

JSONP含義:

JSONP(JSON with Padding)是JSON的一種「使用模式」,它是一個簡單高效的跨域方式,HTML中的script標籤可以加載並執行其他域的javascript,於是我們可以通過script標記來動態加載其他域的資源

JSONP實現原理:

用我覺得最通俗易懂的方式來概括就是:本地有一個函數,它擁有一個參數,這個參數就是我們要跨域獲得的信息。我們通過<script>標籤加載跨域的JS文件,加載時傳遞兩個參數,告訴它第一個是我們需要什麼數據,第二個是它需要執行的函數的名字,現在遠程的JS文件就會將我們需要獲得的信息以JSON的形式作爲參數傳遞到我們告訴的函數裏面,並執行這個函數。即遠程JS傳參數給了本地函數,這樣本地函數就獲得了我們想要的數據。

專業概括爲:首先本地定義回調函數,然後網頁動態插入<script>元素,由它向跨源網址發出請求,跨源網址src內包含兩個參數(callback、info),callback值爲回調函數名字,info值爲所要查詢的信息。跨域服務器收到請求後,將所請求的信息數據以JSON的形式放於回調函數的參數位置返回。回調函數以調用對象的方式進行使用參數。

具體請看如下代碼:

本地:

//動態創建<script>標籤的函數
function addScriptTag(src){
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
//頁面加載完畢時執行addScriptTag()函數
window.onload = function () {
  addScriptTag('協議+域名+端口?info=needInfo?callback=callbackName');
}
//回調函數
function callbackName(data) {
  console.log('遠程返回的JSON數據爲:'+data);
}
跨域服務器:
callbackName({"infoOne":"one","infoTwo":"two",……});
具體看到這裏,相信大家對基本的實現原理已經清楚了,至於服務器端怎麼實現根據參數來調用回調函數的方法就不在這裏進行演示了,無非就是根據判斷拼接字符串罷了。
JSONP的優缺:

a、它的優點非常顯著,主要表現在它簡單適用,老式瀏覽器全部支持,服務器改造非常小

b、它的缺點相信大家也看出來了,由於是通過<script>標籤來實現,所以它只能發送GET請求

c、還有一點就是判斷它請求是否成功不容易,目前大部分框架都是通過結合超時的時間來判定的

好了,JSONP大概就介紹到這裏了。啥啥啥?你用的是JQuery?好吧,再給你們說一下JQuery的實現方法吧

JSONP的JQuery實現方法:

jQuery(document).ready(function(){
    $.ajax({
        //請求方式
        type: "GET",
        //默認爲true,表示異步請求,false表示同步請求
        async: true,
        //跨域請求的地址
        url: "協議+域名+端口?info=needInfo",
        //返回的數據類型
        dataType: "jsonp",
        //傳遞給請求處理程序或頁面,用以獲得jsonp回調函數名的參數名(一般默認爲:callback)
        jsonp: "info",
        //傳遞給請求處理程序或頁面,用以獲得jsonp回調函數名的參數名的值
        data:"needInfo",
        //自定義jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動爲你處理數據
        jsonpCallback:"callbackName",
        //成功調用函數
        success: function(data){
            alert('遠程返回的JSON數據爲:'+data);
        },
        //異常處理函數
        error: function(){
            alert('Fail!');
        }
    });
});
這就是JQuery執行JSONP時所用到的一些基本步驟方法。其中注意一點,我們雖然給予了回調函數名稱,但是並沒有寫回調函數,這是因爲JQuery自動幫你生成了回調函數,並且將參數提取出來供success方法來使用。哇,老鐵,意不意外,驚不驚喜?

document.domain

還有一種比較簡單的方法,它是通過修改document的domain屬性,讓我們可以在域和子域或者不同的子域之間通信。

同域策略認爲域和子域隸屬於不同的域,比如www1.xxx.com和 www2.xxx.com是不同的域,這時,我們無法在www1.xxx.com下的頁面中調用www2.xxx.com中定義的JavaScript方法。但是當我們把它們document的domain屬性都修改爲xxx.com,瀏覽器就會認爲它們處於同一個域下,那麼我們就可以互相調用對方的method來通信了。並且二者可以共享cookie!

好了,規避方法暫時就總結這比較常用的三種,還有一些比較實用的有HTML5新引進的跨文檔API(window.postMessage)或者WebSocket等之後再進行總結