[1]引入 [2]iframe [3]domain屬性 [4]錨點值 [5]XDM ...
前面的話
script、image、iframe的src都不受同源策略的影響。所以可以藉助這一特點,實現跨域。例如,前面介紹的jsonp是使用script標簽,imgPing是使用image標簽,而本文將介紹使用iframe標簽實現跨域
引入
1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。最初,它的含義是指,A 網頁設置的 Cookie,B 網頁不能打開,除非這兩個網頁“同源”。所謂“同源”指的是”三個相同“:1、協議相同;2、功能變數名稱相同;3、埠相同
舉例來說,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:不同源(埠不同) https://www.example.com/dir/page.html:不同源(協議不同)
同源政策的目的,是為了保證用戶信息的安全,防止惡意的網站竊取數據。
設想這樣一種情況:A 網站是一家銀行,用戶登錄以後,又去瀏覽其他網站。如果其他網站可以讀取 A 網站的 Cookie,會發生什麼?很顯然,如果 Cookie 包含隱私(比如存款總額),這些信息就會泄漏。更可怕的是,Cookie 往往用來保存用戶的登錄狀態,如果用戶沒有退出登錄,其他網站就可以冒充用戶,為所欲為。因為瀏覽器同時還規定,提交表單不受同源政策的限制。
由此可見,“同源政策”是必需的,否則 Cookie 可以共用,互聯網就毫無安全可言了
隨著互聯網的發展,“同源政策”越來越嚴格。目前,如果非同源,共有三種行為受到限制
1、Cookie、LocalStorage 和 IndexedDB 無法讀取
2、DOM 無法獲得
3、AJAX 請求無效(可以發送,但瀏覽器會拒絕接受響應)
雖然這些限制是必要的,但是有時很不方便,合理的用途也受到影響
iframe
iframe
元素可以在當前網頁之中,嵌入其他網頁。每個iframe
元素形成自己的視窗,即有自己的window
對象。iframe
視窗之中的腳本,可以獲得父視窗和子視窗。但是,只有在同源的情況下,父視窗和子視窗才能通信;如果跨域,就無法拿到對方的DOM
[註意]關於iframe的詳細信息移步至此
比如,父視窗和子視窗的代碼如下所示,都處於localhost域下
<!-- 父視窗test.html--> <body> <iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){ var doc = iframe.contentWindow.document; console.log(doc.getElementById('test').innerHTML);//'xiaohuochai' console.log(document.cookie);//'name=match' } </script> </body> <!-- 子視窗iframe.html--> <body> <div id="test">xiaohuochai</div> <script> document.cookie = 'name=match'; </script> </body>
如果iframe
視窗不是同源,如處於文件域下(file:///C:/Users/Administrator/Desktop/demo/js/test.html),就會報錯
<iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){
//Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame. console.log(iframe.contentWindow.document); } </script>
上面命令中,父視窗想獲取子視窗的DOM,因為跨域導致報錯。
反之亦然,子視窗獲取主視窗的DOM也會報錯。
window.parent.document.body // 報錯
這種情況不僅適用於iframe
視窗,還適用於window.open
方法打開的視窗,只要跨域,父視窗與子視窗之間就無法通信
domain屬性
如果兩個視窗一級功能變數名稱相同,只是二級功能變數名稱不同,可以通過設置document.domain來使其通信
父視窗地址為https://static.xiaohuochai.site/test/test.html
子視窗地址為https://demo.xiaohuochai.site/test/iframe.html
代碼如下
<!-- 父視窗test.html--> <body> <iframe id="myIFrame" src="https://demo.xiaohuochai.site/test/iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); iframe.onload = function(){ var doc = iframe.contentWindow.document; console.log(doc.getElementById('test').innerHTML);//'xiaohuochai' console.log(document.cookie); } </script> </body> <!-- 子視窗iframe.html--> <body> <div id="test">xiaohuochai</div> <script> document.cookie = 'name=match'; </script> </body>
由結果所示,通過設置document.domain只能獲取DOM,而Cookie、LocalStorage 和 IndexedDB 無法讀取
錨點值
錨點值,又稱為片段標識符(fragment identifier),指的是URL的#
號後面的部分,比如http://example.com/x.html#fragment
的#fragment
。如果只是改變片段標識符,頁面不會重新刷新
父視窗可以把信息,寫入子視窗的錨點值
var src = originURL + '#' + data; document.getElementById('myIFrame').src = src;
子視窗通過監聽hashchange
事件得到通知
window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... }
同樣的,子視窗也可以改變父視窗的片段標識符
parent.location.href= target + '#' + hash;
下麵是具體代碼
<!-- 父視窗test.html--> <body> <iframe id="myIFrame" src="iframe.html"></iframe> <script> var iframe = document.getElementById("myIFrame"); window.onhashchange = function (e) { console.log(/.*#(.*)/g.exec(e.newURL)[1])//'xiaohuochai' } </script> </body> <!-- 子視窗iframe.html--> <body> <div id="test">xiaohuochai</div> <script> parent.location.href = 'test.html' + '#' + test.innerHTML; </script> </body>
XDM
上面兩種方法都屬於破解,HTML5為瞭解決這個問題,引入了一個全新的API:跨文檔通信 API
[註意]IE8-瀏覽器不支持
跨文檔消息傳送(cross-document messaging),有時候簡稱為XDM,指的是在來自不同域的頁面間傳遞消息。例如,www.wrox.com域中的頁面與位於一個內嵌框架中的p2p.wrox.com域中的頁面通信。 在XDM機制出現之前,要穩妥地實現這種通信需要花很多工夫。XDM把這種機制規範化,讓我們能既穩妥又簡單地實現跨文檔通信
XDM的核心是postMessage ()方法。在HTML5規範中,除了 XDM部分之外的其他部分也會提到這個方法名,但都是為了同一個目的:向另一個地方傳遞數據。對於XDM而言,“另一個地方”指的是包含在當前頁面中的<iframe>元素,或者由當前頁面彈出的視窗
postMessage()方法接收兩個參數:一條消息和一個表示消息接收方來自哪個域的字元串。第二個參數對保障安全通信非常重要,可以防止瀏覽器把消息發送到不安全的地方
來看下麵的例子。
//註意:所有支持XDM的瀏覽器也支持ifraaie的contentWindow屬性 var iframeWindow = document.getElementById("rayframe").contentWindow, iframeWindow.postMessage( "A secret', "http://www.wrox.com");
最後一行代碼嘗試向內嵌框架中發送一條消息,並指定框架中的文檔必須來源於"http://www.wrox.com"域。如果來源匹配,消息會傳遞到內嵌框架中;否則,postMessage()什麼也不做。 這一限制可以避免視窗中的位置在你不知情的情況下發生改變。如果傳給postMessage()的第二個參數是"*",則表示可以把消息發送給來自任何域的文檔,但不推薦這樣做
接收到XDM消息時,會觸發window對象的message事件。這個事件是以非同步形式觸發的,因此從發送消息到接收消息(觸發接收視窗的message事件)可能要經過一段時間的延遲。觸發message事件後,傳遞給onmessage處理程式的事件對象包含以下三方面的重要信息
data:作為postMessage()第一個參數傳入的字元串數據 origin:發送消息的文檔所在的域,例如"http://www.wrox.com"。 source:發送消息的文檔的window對象的代理。這個代理對象主要用於在發送上一條消息的視窗中調用postMessage()方法。如果發送消息的視窗來自同一個域,那這個對象就是window
接收到消息後驗證發送視窗的來源是至關重要的。就像給postMessage()方法指定第二個參數, 以確保瀏覽器不會把消息發送給未知頁面一樣,在onmessage處理程式中檢測消息來源可以確保傳入的消息來自已知的頁面。基本的檢測模式如下
window.onmessage = function(e){ if(e.origin == 'http://www.wrox.com'){ //處理接收到的數據 processMessage(e.data); //可選:向來源視窗發送回執 e.source.postMessage("Received!", "http//p2p.wrox.com"); } }
[註意]event.source大多數情況下只是window對象的代理,並非實際的window對象。換句話說,不能通過這個代理對象訪問window對象的其他任何信息。只通過這個代理調用 postMessage()就好,這個方法永遠存在,永遠可以調用
XDM還有一些怪異之處
postMessage()的第一個參數最早是作為“永遠都是字元串”來實現的。但後來這個參數的定義改了,改成允許傳入任何數據結構。可是,並非所有瀏覽器都實現了這一變化。為保險起見,使用postMessage()時,最好還是只傳字元串。如果想傳入結構化的數據,最佳選擇是先在要傳入的數據上調用JSON.stringify(),通過postMessage()傳入得到的字元串,然 後再在onmessage事件處理程式中調用JSON.parse()
在通過內嵌框架載入其他域的內容時,使用XDM是非常方便的。因此,在混搭(mashup)和社交網路應用中,這種傳遞消息的方法極為常用。有了XDM,包含<iframe>的頁面可以確保自身不受惡意內容的侵擾,因為它只通過XDM與嵌入的框架通信。而XDM也可以在來自相同域的頁面間使用
下麵是一個實例
父視窗地址為https://static.xiaohuochai.site/test/test_1.html
子視窗地址為https://demo.xiaohuochai.site/test/iframe_1.html
代碼如下
<!-- 父視窗test_1.html--> <body> <iframe id="myIFrame" src="https://demo.xiaohuochai.site/test/iframe_1.html"></iframe> <script> window.onmessage = function (e) { if(e.origin === 'https://demo.xiaohuochai.site'){ console.log(e.data);//'xiaohuochai' } } </script> </body> <!-- 子視窗iframe_1.html--> <body> <script> if (window.parent !== window.self) { window.parent.postMessage('xiaohuochai', 'https://static.xiaohuochai.site'); } </script> </body>