老生常談的跨域處理
閱讀目錄
- 什么是跨域
- 常用的幾種跨域處理方法:
- 跨域的原理解析及實(shí)現(xiàn)方法
- 總結(jié)
摘要:跨域問(wèn)題,無(wú)論是面試還是平時(shí)的工作中,都會(huì)遇到,本文總結(jié)處理跨域問(wèn)題的幾種方法以及其原理,也讓自己搞懂這方面的知識(shí),走起。
什么是跨域
在JavaScript中,有一個(gè)很重要的安全性限制,被稱(chēng)為“Same-Origin Policy”(同源策略)。這一策略對(duì)于JavaScript代碼能夠訪問(wèn)的頁(yè)面內(nèi)容做了很重要的限制,即JavaScript只能訪問(wèn)與包含它的文檔在同一域下的內(nèi)容。
JavaScript這個(gè)安全策略在進(jìn)行多iframe或多窗口編程、以及Ajax編程時(shí)顯得尤為重要。根據(jù)這個(gè)策略,在baidu.com下的頁(yè)面中包含的JavaScript代碼,不能訪問(wèn)在google.com域名下的頁(yè)面內(nèi)容;甚至不同的子域名之間的頁(yè)面也不能通過(guò)JavaScript代碼互相訪問(wèn)。對(duì)于Ajax的影響在于,通過(guò)XMLHttpRequest實(shí)現(xiàn)的Ajax請(qǐng)求,不能向不同的域提交請(qǐng)求,例如,在abc.example.com下的頁(yè)面,不能向def.example.com提交Ajax請(qǐng)求,等等。
為什么瀏覽器要實(shí)現(xiàn)同源限制?我們舉例說(shuō)明:
比如一個(gè)黑客,他利用iframe把真正的銀行登錄頁(yè)面嵌到他的頁(yè)面上,當(dāng)你使用真實(shí)的用戶(hù)名和密碼登錄時(shí),如果沒(méi)有同源限制,他的頁(yè)面就可以通過(guò)javascript讀取到你的表單中輸入的內(nèi)容,這樣用戶(hù)名和密碼就輕松到手了.
又比如你登錄了OSC,同時(shí)瀏覽了惡意網(wǎng)站,如果沒(méi)有同源限制,該惡意 網(wǎng)站就可以構(gòu)造AJAX請(qǐng)求頻繁在OSC發(fā)廣告帖.
跨域的情況分為以下幾種:

特別注意兩點(diǎn):
1、如果是協(xié)議和端口造成的跨域問(wèn)題“前臺(tái)”是無(wú)能為力的
2、在跨域問(wèn)題上,域僅僅是通過(guò)“URL的首部”來(lái)識(shí)別而不會(huì)去嘗試判斷相同的ip地址對(duì)應(yīng)著兩個(gè)域或兩個(gè)域是否在同一個(gè)ip上。比如上面的,http://www.a.com/a.js和http://70.32.92.74/b.js。雖然域名和域名的ip對(duì)應(yīng),不過(guò)還是被認(rèn)為是跨域。
“URL的首部”指window.location.protocol +window.location.host。其中,
window.location.protocol:指含有URL第一部分的字符串,如http:
window.location.host:指包含有URL中主機(jī)名:端口號(hào)部分的字符串.如//www.cenpok.net/server/
常用的幾種跨域處理方法:
1、JSONP
2、CORS策略
3、document.domain+iframe的設(shè)置
4、HTML5的postMessage
5、使用window.name來(lái)進(jìn)行跨域
跨域的原理解析及實(shí)現(xiàn)方法
1、JSONP(JSON with padding)
原理 :
我們知道,在頁(yè)面上有三種資源是可以與頁(yè)面本身不同源的。它們是:js腳本,css樣式文件,圖片,像淘寶等大型網(wǎng)站,肯定會(huì)將這些靜態(tài)資源放入cdn中,然后在頁(yè)面上連接,如下所示,所以它們是可以鏈接訪問(wèn)到不同源的資源的。
1)<script type="text/javascript" src="某某cdn地址" ></script>
2)<link type="text/css" rel="stylesheet" href="某個(gè)cdn地址" />
3)<img src="某個(gè)cdn地址" alt=""/>
而jsonp就是利用了script標(biāo)簽的src屬性是沒(méi)有跨域的限制的,從而達(dá)到跨域訪問(wèn)的目的。因此它的最基本原理就是:動(dòng)態(tài)添加一個(gè)<script>標(biāo)簽來(lái)實(shí)現(xiàn)。
實(shí)現(xiàn)方法:
這里是使用ajax來(lái)請(qǐng)求的,看起來(lái)和ajax沒(méi)啥區(qū)別,其實(shí)還是有區(qū)別的。
ajax的核心是通過(guò)XmlHttpRequest獲取非本頁(yè)內(nèi)容,而jsonp的核心則是動(dòng)態(tài)添加<script>標(biāo)簽來(lái)調(diào)用服務(wù)器提供的js腳本。
$.ajax({
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
// some code
}
});
上面的代碼中,callback是必須的,callback是什么值要跟后臺(tái)拿。獲取到的jsonp數(shù)據(jù)格式如下:
flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});
jsonp的全稱(chēng)為json with padding,上面的數(shù)據(jù)中,flightHandler就是那個(gè)padding.
JSONP的不足之處:
1、只能使用get方法,不能使用post方法:
我們知道 script,link, img 等等標(biāo)簽引入外部資源,都是 get 請(qǐng)求的,那么就決定了 jsonp 一定是 get 的。但有時(shí)候我們使用的 post 請(qǐng)求也成功,為啥呢?這是因?yàn)楫?dāng)我們指定dataType:'jsonp',不論你指定:type:"post" 或者type:"get",其實(shí)質(zhì)上進(jìn)行的都是 get 請(qǐng)求!
2、沒(méi)有關(guān)于 JSONP 調(diào)用的錯(cuò)誤處理。如果動(dòng)態(tài)腳本插入有效,就執(zhí)行調(diào)用;如果無(wú)效,就靜默失敗。失敗是沒(méi)有任何提示的。例如,不能從服務(wù)器捕捉到 404 錯(cuò)誤,也不能取消或重新開(kāi)始請(qǐng)求。不過(guò),等待一段時(shí)間還沒(méi)有響應(yīng)的話,就不用理它了。
2、CORS策略
原理:
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱(chēng)是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制。它為Web服務(wù)器定義了一種方式,允許網(wǎng)頁(yè)從不同的域訪問(wèn)其資源.
CORS系統(tǒng)定義了一種瀏覽器和服務(wù)器交互的方式來(lái)確定是否允許跨域請(qǐng)求。 它是一個(gè)妥協(xié),有更大的靈活性,但比起簡(jiǎn)單地允許所有這些的要求來(lái)說(shuō)更加安全。
實(shí)現(xiàn)方法:
CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個(gè)CORS通信過(guò)程,都是瀏覽器自動(dòng)完成,不需要用戶(hù)參與。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),CORS通信與同源的AJAX通信沒(méi)有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶(hù)不會(huì)有感覺(jué)。
前端方面
以前我們使用Ajax,代碼類(lèi)似于如下的方式:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/hfahe", true);
xhr.send();
// 這里的“/hfahe”是本域的相對(duì)路徑。
如果我們要使用CORS,相關(guān)Ajax代碼可能如下所示:
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://blog.csdn.net/hfahe", true);
xhr.send();
// 請(qǐng)注意,代碼與之前的區(qū)別就在于相對(duì)路徑換成了其他域的絕對(duì)路徑,也就是你要跨域訪問(wèn)的接口地址。
服務(wù)器方面
服務(wù)器端對(duì)于CORS的支持,主要就是通過(guò)設(shè)置Access-Control-Allow-Origin來(lái)進(jìn)行的。如果瀏覽器檢測(cè)到相應(yīng)的設(shè)置,就可以允許Ajax進(jìn)行跨域的訪問(wèn)。
CORS策略的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
1、CORS支持所有類(lèi)型的HTTP請(qǐng)求。
2、 使用CORS,開(kāi)發(fā)者可以使用普通的XMLHttpRequest發(fā)起請(qǐng)求和獲得數(shù)據(jù),比起JSONP有更好的錯(cuò)誤處理。
缺點(diǎn): 兼容性方面相對(duì)差一點(diǎn),ie10或以上才支持
3、document.domain+iframe的設(shè)置 (只有在主域相同的時(shí)候才能使用該方法)
原理:
瀏覽器中不同域的框架之間是不能進(jìn)行js的交互操作的。但是不同的框架之間(父子或同輩),是能夠獲取到彼此的window對(duì)象的,但是,我們也只能獲取到一個(gè)幾乎
無(wú)用的window對(duì)象。比如,有一個(gè)頁(yè)面,它的地址是 http://www.example.com/a.html , 在這個(gè)頁(yè)面里面有一個(gè)iframe,它的src是 http://example.com/b.html , 很顯然,這
個(gè)頁(yè)面與它里面的iframe框架是不同域的,所以我們是無(wú)法通過(guò)在頁(yè)面中書(shū)寫(xiě)js代碼來(lái)獲取iframe中的東西的。
這個(gè)時(shí)候,document.domain就可以派上用場(chǎng)了,我們只要把 http://www.example.com/a.html 和 http://example.com/b.html 這兩個(gè)頁(yè)面的document.domain都設(shè)成
相同的域名就可以了。但要注意的是,document.domain的設(shè)置是有限制的,我們只能把document.domain設(shè)置成自身或更高一級(jí)的父域,且主域必須相同。例如:
a.b.example.com 中某個(gè)文檔的document.domain 可以設(shè)成a.b.example.com、b.example.com 、example.com中的任意一個(gè),但是不可以設(shè)成 c.a.b.example.com,因?yàn)檫@是
當(dāng)前域的子域,也不可以設(shè)成baidu.com,因?yàn)橹饔蛞呀?jīng)不相同了。
使用方法:
比如在http://www.example.com/a.html 的頁(yè)面里要訪問(wèn) http://example.com/b.html里面的東西。
在頁(yè)面 http://www.example.com/a.html 中設(shè)置document.domain:
//http://www.example.com/a.html
<html>
<head>
<title>A頁(yè)面</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div>A頁(yè)面</div>
<iframe id="iframe" src="http://example.com/b.html" style="display:none;"></iframe>
// 相當(dāng)于用一個(gè)隱藏的iframe來(lái)做代理
<script>
$(function(){
try{
document.domain = "example.com"; //這里將document.domain設(shè)置成一樣
}catch(e){}
$("#iframe").load(function(){
var iframe = $("#iframe").contentDocument.$;
ifram.get("http://example.com/接口",function(data){});
});
});
</script>
<body>
</html>
在頁(yè)面 http://example.com/b.html 中也設(shè)置document.domain,而且這也是必須的,雖然這個(gè)文檔的domain就是example.com,但是還是必須顯示的設(shè)置document.domain的值:
//http://example.com/b.html
<html>
<head>
<title>B頁(yè)面</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div>B頁(yè)面</div>
<script>
$(function(){
try{
document.domain = "example.com"; //這里將document.domain設(shè)置成一樣
}catch(e){}
});
</script>
</body>
</html>
這里有個(gè)注意點(diǎn),就是在A頁(yè)面中,要等iframe標(biāo)簽完成加載B頁(yè)面之后,再取iframe對(duì)象的contentDocument,否則如果B頁(yè)面沒(méi)有被iframe完全加載,在A頁(yè)面中通過(guò)contentDocument屬性就取不到B頁(yè)面中的jQuery對(duì)象。
一旦取到B頁(yè)面中的jQuery對(duì)象,就可以直接發(fā)ajax請(qǐng)求了,這種類(lèi)似“代理”方式可以解決主子域的跨域問(wèn)題。
缺點(diǎn):
只有在主域相同的時(shí)候才能使用該方法
4、HTML5的postMessage
原理:
沒(méi)啥原理,就是一個(gè)html5所提供的一個(gè)API.--->HTML5 window.postMessage是一個(gè)安全的、基于事件的消息API。
在需要發(fā)送消息的源窗口調(diào)用postMessage方法即可發(fā)送消息。其中.源窗口可以是全局的window對(duì)象,也可以是以下類(lèi)型的窗口:
1、文檔窗口中的iframe:
var iframe = document.getElementById('my-iframe');
var win = iframe.documentWindow;
2、JavaScript打開(kāi)的彈窗:
var win = window.open();
3、當(dāng)前文檔窗口的父窗口:
var win = window.parent;
4、
var win = window.opener();
發(fā)送消息:找到源window對(duì)象后,即可調(diào)用postMessage API向目標(biāo)窗口發(fā)送消息:
win.postMessage(msg, targetOrigin);
說(shuō)明:postMessage函數(shù)接收兩個(gè)參數(shù):
1、msg, 將要發(fā)送的消息,可以使一切javascript參數(shù),如字符串,數(shù)字,對(duì)象,數(shù)組等。
2、targetOrigin,這個(gè)參數(shù)稱(chēng)作“目標(biāo)域”,注意,是目標(biāo)域不是本域!比如,你想在2.com的網(wǎng)頁(yè)上往1.com網(wǎng)頁(yè)上傳消息,那么這個(gè)參數(shù)就是“http://1.com/”,而不是2.com.協(xié)議,(一個(gè)完整的域名包括:主機(jī)名,端口號(hào)。如:http://g.cn:80/)
接收消息:那目標(biāo)窗口要怎么接收傳過(guò)來(lái)的數(shù)據(jù)呢,只要監(jiān)聽(tīng)window的message事件就可以接收了。
var onmessage = function (event) {
var data = event.data;
var origin = event.origin;
//do someing
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}
message事件監(jiān)聽(tīng)函數(shù)接收一個(gè)參數(shù),Event對(duì)象實(shí)例,該對(duì)象有三個(gè)屬性:
- data : 消息
- origin:消息的來(lái)源地址
- source:發(fā)送消息窗口的window對(duì)象引用
使用方法(案例):
http://test.com/index.html--> 發(fā)送消息的頁(yè)面
<!-- 這個(gè)是 http://test.com/index.html 頁(yè)面 -->
<div>
<!-- 要給下面的頁(yè)面?zhèn)饕粋€(gè)妹子過(guò)去 -->
<iframe id="child" src="http://lsLib.com/lsLib.html"></iframe>
</div>
<script type="text/javascript">
window.onload=function(){
window.frames[0].postMessage('蒼老師','http://lslib.com');
}
</script>
http://lslib.com/lslib.html --> 接收消息的頁(yè)面
<!-- 這個(gè)是 http://lslib.com/lslib.html 頁(yè)面 -->
<script type="text/javascript">
window.addEventListener('message',function (e) {
console.log(e.origin,e.data);
alert('收到妹子一枚:'+e.data);
});
</script>
優(yōu)缺點(diǎn):
優(yōu)點(diǎn):方便,安全,有效的解決了跨域問(wèn)題
缺點(diǎn):萬(wàn)惡的資本主義,ie8+才支持,而且ie8+<ie10只支持iframe的方式。
5、使用window.name來(lái)進(jìn)行跨域(相對(duì)比較完美的方法)
原理:
當(dāng)iframe的頁(yè)面跳到其他地址時(shí),其window.name值保持不變,并且可以支持非常長(zhǎng)的 name 值(2MB)。
瀏覽器跨域iframe禁止互相調(diào)用/傳值.但是調(diào)用iframe時(shí) window.name 卻不變,正是利用這個(gè)特性來(lái)互相傳值,當(dāng)然跨域下是不容許讀取ifram的window.name值.
所以這里我們還要準(zhǔn)備一個(gè)和主頁(yè)面http://www.a.com/main.html 相同域下的代理頁(yè)面http://www.a.com/other.html ,iframe調(diào)用子頁(yè)面 http://www.b.com/data.html
使用方法:
1、準(zhǔn)備三個(gè)頁(yè)面:
http://www.a.com/main.html //應(yīng)用頁(yè)面
http://www.a.com/other.html // 代理頁(yè)面,要求和應(yīng)用頁(yè)面在同一個(gè)域。一般是一個(gè)空的html
http://www.b.com/data.html //應(yīng)用頁(yè)面獲取數(shù)據(jù)的頁(yè)面,簡(jiǎn)稱(chēng):數(shù)據(jù)頁(yè)面
2、數(shù)據(jù)頁(yè)面將數(shù)據(jù)傳到window.name中去。如下:
http://www.b.com/data.html中的 data.html
// data.html window.name="蒼老師"; //可以是其他類(lèi)型的數(shù)據(jù),比如數(shù)組,對(duì)象等等
http://www.a.com/main.html //應(yīng)用頁(yè)面的代碼如下:
<!-- main.html -->
var iframeData;
var state = 0;//開(kāi)關(guān)變量
var iframe = document.createElement('iframe'); //創(chuàng)建iframe
var loadfn = function() {
if (state === 1) {
iframeData = iframe.contentWindow.name; // 讀取數(shù)據(jù)
alert('獲取到了iframe傳過(guò)來(lái)的妹子'+iframeData);
}else if (state === 0) {
state = 1;
iframe.contentWindow.location = 'http://www.a.com/other.html'; //這里是代理頁(yè)面 other.html
/**
這里說(shuō)明一下:
由于iframe的location改變了,相當(dāng)于重新載入頁(yè)面(這是iframe的性質(zhì)決定的),于是重新執(zhí)行l(wèi)oadfn方法。
由于當(dāng)iframe的頁(yè)面跳到其他地址時(shí),其window.name值保持不變,并且此時(shí)開(kāi)關(guān)變量 state已經(jīng)變?yōu)?,
于是就可以獲取到window.name值,也就達(dá)到了跨域訪問(wèn)的目的了。
**/
};
}
iframe.src = 'http://www.b.com/data.html'; //這是是數(shù)據(jù)頁(yè)面,data.html
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
3、獲取數(shù)據(jù)以后銷(xiāo)毀這個(gè)iframe,釋放內(nèi)存;這也保證了安全(不被其他域frame js訪問(wèn))。
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
優(yōu)缺點(diǎn):
瀏覽器支持情況好,是比較普遍的使用方法
總結(jié)
以上總結(jié)了js跨域的幾種方法,當(dāng)然還有其他的方法,不過(guò)沒(méi)有。他們各有千秋。其實(shí)最主要的區(qū)別除了實(shí)現(xiàn)方式不一樣,主要是瀏覽器的兼容問(wèn)題而已。
JSONP:
JSONP的優(yōu)點(diǎn)是:它不像XMLHttpRequest對(duì)象實(shí)現(xiàn)的Ajax請(qǐng)求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中都可以運(yùn)行,不需要XMLHttpRequest或ActiveX的支持;并且在請(qǐng)求完畢后可以通過(guò)調(diào)用callback的方式回傳結(jié)果。
JSONP的缺點(diǎn)則是:它只支持GET請(qǐng)求而不支持POST等其它類(lèi)型的HTTP請(qǐng)求;它只支持跨域HTTP請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁(yè)面之間如何進(jìn)行JavaScript調(diào)用的問(wèn)題。
CORS策略
優(yōu)點(diǎn):使用CORS,開(kāi)發(fā)者可以使用普通的XMLHttpRequest發(fā)起請(qǐng)求和獲得數(shù)據(jù),比起JSONP有更好的錯(cuò)誤處理。
缺點(diǎn):古老的瀏覽器不支持,不過(guò)大部分現(xiàn)代瀏覽器都支持
document.domain+iframe:只適用于主域相同的跨域問(wèn)題處理
html5的postMessage:
優(yōu)點(diǎn):是html5新引進(jìn)的特性,可以使用它來(lái)向其它的window對(duì)象發(fā)送消息,無(wú)論這個(gè)window對(duì)象是屬于同源或不同源,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經(jīng)支持window.postMessage方法。如果是現(xiàn)代瀏覽器,首選。
缺點(diǎn): ie8以前不支持
window.name:
主要是應(yīng)用當(dāng)frame的頁(yè)面跳到其他地址時(shí),其window.name值保持不變的原理。兼容性好。需要照顧落后的瀏覽器時(shí),首選。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
webpack開(kāi)發(fā)跨域問(wèn)題解決辦法
本篇文章主要介紹了webpack開(kāi)發(fā)跨域問(wèn)題解決辦法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
關(guān)于JS控制代碼暫停的實(shí)現(xiàn)方法分享
關(guān)于JS控制代碼暫停的工作總結(jié),需要的朋友可以參考下2012-10-10
Bootstrap+jfinal退出系統(tǒng)彈出確認(rèn)框的實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Bootstrap+jfinal退出系統(tǒng)彈出框的實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-05-05
JSON數(shù)據(jù)中存在單個(gè)轉(zhuǎn)義字符“\”的處理方法
這篇文章主要介紹了JSON數(shù)據(jù)中存在單個(gè)轉(zhuǎn)義字符“\”的處理方法,在這里反斜杠(又稱(chēng)右斜杠"\"),還表示轉(zhuǎn)義字符,字符串中不能成單出現(xiàn)。具體內(nèi)容詳情大家跟隨腳本之家小編一起看看吧2018-07-07
javascript正則表達(dá)式之分組概念與用法實(shí)例
這篇文章主要介紹了javascript正則表達(dá)式之分組概念與用法,結(jié)合實(shí)例形式分析了javascript正則表達(dá)式分組的功能、定義與使用方法,需要的朋友可以參考下2016-06-06
原生js實(shí)現(xiàn)鍵盤(pán)控制div移動(dòng)且解決停頓問(wèn)題
這篇文章主要給大家介紹了如何利用原生js實(shí)現(xiàn)鍵盤(pán)控制div移動(dòng),并且解決在移動(dòng)過(guò)程中的停頓問(wèn)題,文中給出了詳細(xì)的示例代碼,相信對(duì)大家的理解和學(xué)習(xí)很有幫助,下面跟著小編一起來(lái)看看吧。2016-12-12
關(guān)于取不到由location.href提交而來(lái)的上級(jí)頁(yè)面地址的解決辦法
驗(yàn)證上級(jí)頁(yè)面來(lái)源取不到由location.href提交而來(lái)的頁(yè)面地址,搜索了一大堆沒(méi)有合適的解決辦法,突然想到通過(guò)模擬JS點(diǎn)擊鏈接的方法2009-07-07
javascript中Date對(duì)象的getDay方法使用指南
這篇文章主要介紹了javascript中Date對(duì)象的getDay方法使用以及各種優(yōu)化方案,非常不錯(cuò)的文章,這里推薦給大家。2014-12-12

