1. 程式人生 > >[整理]JavaScript跨域解決方法大全

[整理]JavaScript跨域解決方法大全

跨域的定義:Javascript出於安全性考慮,同源策略機制對跨域訪問做了限制。域僅僅是通過“URL的首部”字串進行識別,“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。實際上,同源策略就是瀏覽器的一種保護機制,只要請求雙方的URL協議、域名(主機)、埠有任何一個不同,都被當作是跨域。

同源策略機制從 Netscape Navigator 2.0 版本開始就存在,同源策略只對HTML文件有效,同源策略不阻止動態指令碼插入。


例如:

URL 說明 是否允許通訊
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同資料夾 允許
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同埠 不允許
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同協議 不允許
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名對應ip 不允許
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允許
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二級域名(同上) 不允許(cookie這種情況下也不允許訪問)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名 不允許


基於上述同源策略,我們可以有兩種方式來實現跨域訪問。

第一種方法是 構造同域,人為將其通訊雙方設為同一域名下。

第二種方法是 規避跨域,使用iframe或動態指令碼、其他頁面公共物件屬性來傳值通訊。


總結出來有以下幾種方法:

1、使用document.domain和iframe實現


對於主域名相同,二級子域名不同的情況,可以使用document.domain重置為相同的一級域名。主域名是不帶www的域名,某一頁面的domain預設等於window.location.hostname,這樣設定擴大了同域的範圍。如此便可互相通訊。
例子:
www.mydomain.com上的a.html


document.domain = 'mydomain.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://game.mydomain.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 在這裡操縱b.html
    alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};

game.mydomain.com上的b.html

document.domain = 'mydomain.com';

這種方法擴大了安全性的存在範圍,兩個域名下都有可能出現安全隱患,若一個頁面中引入多個iframe需要跨子域名通訊,要想能夠操作所有iframe,須全部設定相同domain。

2、動態建立script

雖然瀏覽器預設禁止了跨域訪問,但並不禁止在頁面中引用其他域的JS檔案,並可以自由執行引入的JS檔案中的function(包括操作cookie、Dom等等)。根據這一點,可以方便地通過建立script節點的方法來實現完全跨域的通訊。具體的做法可以參考YUI的Get Utility

這裡判斷script節點載入完畢還是蠻有意思的:ie只能通過script的readystatechange屬性,其它瀏覽器是script的load事件。以下是部分判斷script載入完畢的方法。

js.onload = js.onreadystatechange = function() {
    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        // callback在此處執行
        js.onload = js.onreadystatechange = null;
    }
};

3、利用iframe和location.hash

window.location.hash屬性是一可讀寫字串,該字串是 URL 的錨部分(從 # 號開始的部分)。這個辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進行傳值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改變hash並不會導致頁面重新整理,所以可以利用hash值來進行資料傳遞,當然資料容量是有限的。假設域名a.com下的檔案cs1.html要和cnblogs.com域名下的cs2.html傳遞資訊,cs1.html首先建立自動建立一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面,這時的hash值可以做引數傳遞用。cs2.html響應請求後再將通過修改cs1.html的hash值來傳遞資料(由於兩個頁面不在同一個域下IE、Chrome不允許修改parent.location.hash的值,所以要藉助於a.com域名下的一個代理iframe;Firefox可以修改)。同時在cs1.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一點有變化則獲取獲取hash值。程式碼如下:

先是a.com下的檔案cs1.html檔案:

function startRequest(){
    var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
    document.body.appendChild(ifr);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1) : '';
        if (console.log) {
            console.log('Now the data is '+data);
        }
    } catch(e) {};
}
setInterval(checkHash, 2000);

cnblogs.com域名下的cs2.html:

//模擬一個簡單的引數處理操作
switch(location.hash){
    case '#paramdo':
        callBack();
        break;
    case '#paramset':
        //do something……
        break;
}

function callBack(){
    try {
        parent.location.hash = 'somedata';
    } catch (e) {
        // ie、chrome的安全機制無法修改parent.location.hash,
        // 所以要利用一箇中間的cnblogs域下的代理iframe
        var ifrproxy = document.createElement('iframe');
        ifrproxy.style.display = 'none';
        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意該檔案在"a.com"域下
        document.body.appendChild(ifrproxy);
    }
}

a.com下的域名cs3.html

//因為parent.parent和自身屬於同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

當然這樣做也存在很多缺點,諸如資料直接暴露在了url中,資料容量和型別都有限等……

另一種寫法:

a頁面(完全跨域):

var iframe = document.getElementById("myiframe"),
	url = iframe.src,
	time = (new Date()).getTime();
if(url.indexOf("timestamps") != -1){
	iframe.src = url.replace(/timestamps=\w*/,"timestamps="+time);
}else {
	iframe.src = url+"/#timestamps="+time;
}
  b頁面:

	window.name="id=333&name=測試名稱哈哈哈" + (new Date()).getTime();


	var data = {}, hash_url = '';
	function dealHash(){
		hash_url = window.location.hash;
		var  dataArr = hash_url.split("#")[1].split("&");
		for(var i = 0;i<dataArr.length;i++){
		    var temp = dataArr[i].split("=");
		    data[temp[0]] = decodeURIComponent(temp[1]);
		}
	}
	function change(){
		if(hash_url!=window.location.hash){
		    dealHash();
		    document.getElementById("overlay").style.display = "block";
		    document.getElementById("overlay").innerHTML = data["id"] + ":" + data["name"];
		    clearInterval(intervalId);
		}
	}
	var intervalId = setInterval(change, 100);


4、window.name實現的跨域資料傳輸

window.name 在瀏覽器環境中是一個全域性/window物件的屬性,且當在 frame 中載入新頁面時,name 的屬性值依舊保持不變。
通過在 iframe 中載入一個資源,該目標頁面將設定 frame 的 name 屬性。此 name 屬性值可被獲取到,以訪問 Web 服務傳送的資訊。
但 name 屬性僅對相同域名的 frame 可訪問。這意味著為了訪問 name 屬性,當遠端 Web 服務頁面被載入後,必須導航 frame 回到原始域。同源策略依舊防止其他 frame 訪問 name 屬性。一旦 name 屬性獲得,銷燬 frame 。

原理核心:window物件的name屬性是一個很特別的屬性,當該window的location變化,然後重新載入,它的name屬性可以依然保持不變。
依此原理,我們可以在頁面A中用iframe載入其他域的頁面B,而頁面B中用JavaScript把需要傳遞的資料賦值給 window.name,頁面A的iframe載入完成之後,頁面A修改iframe的地址,將其變成同域的一個地址,然後就可以讀出window.name的值了。

a頁面:

		function domainData(iframe, url, fn)
		{
			var isFirst = true;
			iframe.style.display = 'none';
			var loadfn = function(){
				if(isFirst){
					//null.html 是同域下的中轉代理頁面。內容可為空。
					iframe.contentWindow.location = 'http://game.feiliu.com/null.html';
					isFirst = false;
				} else {
					fn(iframe.contentWindow.name);
					//使用完畢銷燬iframe,保證安全,釋放記憶體
					iframe.contentWindow.document.write('');
					iframe.contentWindow.close();
					document.body.removeChild(iframe);
					iframe.src = '';
					iframe = null;
				}
			};
			iframe.src = url;
			//alert(url);
			//給iframe設定onload事件
			if(iframe.attachEvent){
				iframe.attachEvent('onload', loadfn);
			} else {
				iframe.addEventListener('load', loadfn, false);
				//iframe.onload = loadfn;
			}
		    
			document.body.appendChild(iframe);
		}
		var ifm = document.getElementById("myiframe");
		setInterval(function(){
			var time = (new Date()).getTime();
			domainData(ifm, 'http://js.8783.com/www/weixin/test.htm?v=28&time=' + time, function(data){
				alert(data);
			});
		}, 1000);

b頁面:

<script type="text/javascript">
    window.name = '測試測試測試!';    // 這裡是要傳輸的資料,大小一般為2M,IE和firefox下可以大至32M左右
                                     // 資料格式可以自定義,如json、字串
</script>


5、使用HTML5 postMessage

HTML5中最酷的新功能之一就是 跨文件訊息傳輸Cross Document 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>

參考文章:《精通HTML5程式設計》第五章——跨文件訊息機制https://developer.mozilla.org/en/dom/window.postmessage

6、利用flash

這是從YUI3的IO元件中看到的辦法,具體可見http://developer.yahoo.com/yui/3/io/
可以看在Adobe Developer Connection看到更多的跨域代理檔案規範:ross-Domain Policy File SpecificationsHTTP Headers Blacklist

7、利用Window.opener hack方式

通過window.name與hash方式都會有url長度限制或者傳值資料量的限制,這時我們可以配合FF/CH/IE8+支援的PostMessage,opener hack結合起來,相容所有瀏覽器。據說是google的工程師率先發現的這個bug,fackbook的登陸頁面就是利用了這個bug實現了postMessage的跨域。

a頁面:

var i=document.getElementById('a');
i.contentWindow.opener={
	dd:function(str){
		var div=document.createElement('div');
		document.body.appendChild(div);
		div.innerHTML=str;
	}
}
setTimeout(function(){
	opener.bb('bbbbbbb');
},300);

b頁面:

window.opener.dd('aaaaaaaaa');
	parent.opener={
		bb:function(str){
			var div=document.createElement('div');
			document.body.appendChild(div);
			div.innerHTML=str;
		}
	}
}	

我們可以看到,在IE6,7下,只要重置了window物件的opener為一個{}物件,在父頁面設定了iframe的window.opener為一個{}之後,在iframe裡面就可以通過opener呼叫parent的方法,在iframe重置parent.opener為一個{}物件之後,在parent就可以呼叫iframe的方法。

*還有一種說法是可以設定opener為function(){},通過new opener()來呼叫

總結,通過IE6,7的hack,我們可以比較完美的實現postMessage在各大主流瀏覽器的相容,以後跨域又多了一項利器。不過比較遺憾的事,重置opener之後,對於window.open開啟的視窗,就不能很好的操作了,在IE6,7下。


文章與程式碼主要來自http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html#m4

相關文章:

iframe跨域通訊的通用解決方案-第二彈 http://www.alloyteam.com/2013/11/the-second-version-universal-solution-iframe-cross-domain-communication/
近乎完美的簡單 JS 跨域解決方式 -- window.name  http://rubel.iteye.com/blog/901182
apache 配置反向代理解決javascript ajax跨域問題  http://www.ghugo.com/apache-reverse-proxy-configuration-problem-solving-ajax-cross-domain/