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

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

