前端跨域數據訪問

術語

同源

請求URL擁有相同protocol、host、port。參考Table 1 同源舉例

用戶認證(User Credential)

在CORS中,用戶認證指cookies, HTTP基本認證,客戶端SSL認證。不指代理端的認證或Origin頭。

缺乏用戶認證是指響應中不包含cookie,而且請求中不包含HTTP認證和SSL證書。

簡單方法(Simple method)

指的是GET, HEAD, POST這三個method。

簡單頭

指Accept, Accept-Language, Content-Language, Content-Type滿足application/x-www-form-urlencoded,multipart/form-data或text/plain。

簡單響應頭

指的是下列幾個header字段:Cache-Control, Content-Language, Content-Type, Expires,Last-Modified, Pragma。

背景

爲了網絡安全,用戶代理(即瀏覽器)對客戶端Web應用程序應用了同源政策,一個域的腳本不能訪問其他域的document。這樣的好處是雖然腳本可以欺騙用戶打開另一個域的敏感頁面,但可以防止腳本讀取敏感數據。

缺點就是給資源共享帶來了困難,跨域數據訪問的目的就是實現跨域的資源共享。

理解同源政策

Script通過src引用,Script的域就是引用它的頁面所在的域,與Script原來在哪無關。

同源政策:Prevents access to DOM on different sites.

理解:假設腳本Script在域A.com中,然後在B.com中有一個頁面通過src引用了A.com的Script。這個Script可以訪問B.com中頁面的任何內容,但如果Script打開一個新窗口並加載A.com或其他域的頁面,根據同源政策,Script不能訪問A.com或其他域中頁面的內容或屬性。雖然可以打開新窗口(並加載其他域的頁面)或關閉這個窗口,但無法訪問窗口內頁面的內容。

解決辦法一  document.domain

如果兩個窗口(windows or frames)各自有自己的腳本同時把document.domain設置爲相同的值,即使不是同源的,兩個窗口依然可以相互訪問。

解決辦法二  JSONP(JSON withPadding)

JSONP是一種跨域手段。

由於同源策略,不同域的網頁不能進行數據溝通,而HTML的script元素是個例外,script元素可以加載不同域的腳本。如果加載的不是腳本,而是動態的JSON資料,則這個就是JSONP了。用script抓取的並不是JSON,而是任意的JavaScript,因此是由JavaScript直譯器執行,而不是用JSON解析器解析。

原理

把<script>元素的src屬性設成一個回傳JSON的URL是可以想象的,這也代表從HTML頁面通過script元素抓取JSON是可能的。

問題是JSON文件不是JavaScript程序,無法執行,從src裏URL回傳的必須是可執行的JavaScript。在JSONP的使用模式裏,這個URL回傳的JSON是由函數呼叫包起來的,這就是JSONP的“填充(padding)”或是“前綴(prefix)”的由來。

慣例上瀏覽器提供回調函數的名稱當作送至服務器的請求中命名查詢參數的一部分,例如:

<scripttype="text/javascript"  src = "http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse">
 </script>

服務器會在傳給瀏覽器前將 JSON 迴應用這個前輟(或稱作填充)包起來。瀏覽器得到的迴應已不是單純的資料敘述而是一個腳本。在本例中,瀏覽器得到的是:

parseResponse({"Name":"Cheeso", "Id" : 1823, "Rank": 7})

填充

填充不一定是瀏覽器頁面中已定義的某個回調函數,也可以是變量賦值,if敘述或其它Javascript敘述。回傳內容可以是任意的運算式,甚至不需要任何的JSON,不過慣例上填充部分還是會觸發函數調用的一小段JavaScript片段,而這個函數呼叫是作用在JSON格式的資料上的。

Script元素“注入”

需要爲每一個JSONP請求加一個新的、有所需src值的script元素到HTML中。

安全問題

從其它域返回的函數或腳本可以訪問該域下任何數據,這有安全問題。爲了達到安全的要求,於是定義了更嚴格的JSON-P,使瀏覽器可以對MIME類型是"application/json-p"的請求做強制處理,如果迴應不能被解析爲嚴格的JSON-P,瀏覽器可以丟出一個錯誤或忽略整個迴應。

JSON-P嚴格模式

就是強制使返回的JSONP滿足這個約束:

functionName({JSON});
 
obj.functionName({JSON});
 
obj["function-name"]({JSON});

 

解決辦法三  CORS (Cross-Origin ResourceSharing)

這是W3C從2005年開始起草的一個標準,專門用於解決跨域請求。

原理

用戶代理通過訪問HTTP響應返回的頭信息來授權認證該資源是否可以訪問當前域。

跨域請求信息

從當前域的頁面給另一個域的URL發送請求,用戶代理會在請求中添加請求頭Origin表示發送請求的域。另外還有請求頭Referer表示發送請求頁的URL。

服務器應用程序可以根據請求頭Origin判斷請求是不是跨域請求,並作出反應是否什麼也不返回(響應頭是要返回的,只是可能沒有響應體)。

跨域響應信息

Web應用程序如果允許其他域的應用程序訪問,服務器可以在響應中添加響應頭Access-Control-Allow-Origin,表示允許訪問的域。用戶代理(瀏覽器)檢測到這個響應頭之後會比較請求的域,如果請求域屬於被允許訪問的域,則發送請求的域可以訪問響應中的內容。

另外,還可以在響應中添加其他響應頭控制允許訪問的時間長度,允許使用什麼方法訪問。

使用經驗

1.      如果某個資源對其它域沒用(比如登陸頁面),就不需要返回Access-Control-Allow-Origin頭。要保護自己被CSRF攻擊,還需要要求在請求中添加一個token來做判斷。

2.      不需要做訪問控制檢查的頁面,可以返回Access-Control-Allow-Origin爲"*"。

3.      一個GET響應的整個內容都可以解析成ECMAScript,可以返回Access-Control-Allow-Origin爲"*"。

有用戶認證或Origin頭的請求需要特殊考慮:

1.      CORS是JSONP,Server-to-Serverback channel,cross-document messaging的替代品。但這個替代也有自己的缺陷,比如,與Server-to-Server back channel相比,被請求的資源有更高的特權。與JSONP相比,則有很好的優越性,因爲JSONP是通過注入代碼來完成跨域的。與cross-document messaging相比,則差不多,因爲對於不完全信任的域的資源還是要做格式驗證或值驗證。

一般請求語法

通過設置請求頭或響應頭的方式,允許不同域的腳本可以訪問當前域的數據。

請求頭

Origin

表明請求的來源。

響應頭

Access-Control-Allow-Origin

允許訪問的域名,“*”號,“null”,或空格分開的字符串。如果請求的域匹配這個響應頭中某個域名,則請求頁面可以訪問響應的內容。

Preflight請求和響應

有些瀏覽器會先發一個請求到另一個域的服務器中,告訴服務器請求大概會是什麼樣子,以詢問服務器是否支持這樣的跨域。這就叫preflight request。

preflight請求頭

Access-Control-Request-Method

表明請求將通過何種方法(post, get和head)去獲取目標域的數據。

Access-Control-Request-Headers

表明請求將所包含的頭信息。

preflight 響應頭

Access-Control-Allow-Methods

告訴請求域,服務器只支持使用這些方法訪問這個域。

Access-Control-Allow-Headers

告訴請求域,你只能在請求中添加這些請求頭。

Access-Control-Allow-Credentials

如果preflight請求暗示實際的請求可能包含用戶認證,這個頭的信息表示在缺乏用戶認證的情況下,是否暴露響應。

"Access-Control-Allow-Credentials"":" true

                            true: %x74.72.75.65; "true", case-sensitive

解決辦法四 XDomainRequest

XDomainRequest是IE8和IE9提供的一個CORS實現,IE10又被刪除了。

使用XDomainRequest需要滿足兩個條件:

  • 請求源的安全協議必須和被請求的URL的協議一致,即http對http,https對https,否則請求返回"Access is Denined"。
  • 請求的服務器必須在響應中設置Acces-Control-Allow-Origin頭保證請求源可以訪問這個服務器。

屬性

解釋

timeout

設置請求超時時間

responseText

 

方法

解釋

open(method, url)

打開一個請求,GET/POST

send(data)

發送請求

abort()

放棄請求

事件處理

解釋

onprogress

 

ontimeout

 

onerror

 

onload

 

索引

同源舉例

Table 1 同源舉例

Compared URL

Outcome

Reason

httpː//www.example.com/dir/page2.html

Success

Same protocol and host

httpː//www.example.com/dir2/other.html

Success

Same protocol and host

httpː//username:password@www.example.com/dir2/other.html

Success

Same protocol and host

httpː//www.example.com:81/dir/other.html

Failure

Same protocol and host but different port

https://www.example.com/dir/other.html

Failure

Different protocol

http://en.example.com/dir/other.html

Failure

Different host

http://example.com/dir/other.html

Failure

Different host (exact match required)

http://v2.www.example.com/dir/other.html

Failure

Different host (exact match required)

httpː//www.example.com:80/dir/other.html

Don't use

Port explicit. Depends on implementation in browser.

postMessage

HTML5中最酷的新功能之一就是 跨文檔消息傳輸CrossDocument Messaging。 下一代瀏覽器都將支持這個功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已經使用了這個功能,用postMessage支持基於web的實時消息傳遞。

otherWindow.postMessage(message,targetOrigin);

otherWindow: 對接收信息頁面的window的引用。可以是頁面中iframe的contentWindow屬性;window.open的返回值;通過name或下標從window.frames取到的值。
message: 所要發送的數據,string類型。
targetOrigin: 用於限制otherWindow,“*”表示不作限制

a.com/index.html中的代碼:

<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://b.com';  // 若寫成'http://b.com/c/proxy.html'效果一樣
                                        // 若寫成'http://c.com'就不會執行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b.com/index.html中的代碼:

<script type="text/javascript">
    window.addEventListener('message', function(event){
        // 通過origin屬性判斷消息來源地址
        if (event.origin == 'http://a.com') {
            alert(event.data);    // 彈出"I was there!"
            alert(event.source);  // 對a.com、index.html中window對象的引用
                                  // 但由於同源策略,這裏event.source不可以訪問window對象
        }
    }, false);
</script>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章