JS跨域問(wèn)題
-
協(xié)議不同
如http, https惕耕; - 端口不同
- 主域相同,子域不同
- 主域不同
-
ip地址和域名
瀏覽器不會(huì)自動(dòng)做ip域名的映射
造成跨域的兩種策略
瀏覽器的同源策略會(huì)導(dǎo)致跨域,這里同源策略又分為以下兩種
DOM同源策略
禁止對(duì)不同源頁(yè)面DOM進(jìn)行操作班眯。這里主要場(chǎng)景是iframe跨域的情況,不同域名的iframe是限制互相訪問(wèn)的。
XmlHttpRequest同源策略
禁止使用XHR對(duì)象向不同源的服務(wù)器地址發(fā)起HTTP請(qǐng)求蓖柔。只要協(xié)議、域名风纠、端口有任何一個(gè)不同况鸣,都被當(dāng)作是不同的域,之間的請(qǐng)求就是跨域操作竹观。
為什么要有跨域限制
了解完跨域之后镐捧,想必大家都會(huì)有這么一個(gè)思考,為什么要有跨域的限制臭增,瀏覽器這么做是出于何種原因呢懂酱。其實(shí)仔細(xì)想一想就會(huì)明白,跨域限制主要是為了安全考慮誊抛。
AJAX同源策略主要用來(lái)防止CSRF攻擊列牺。如果沒(méi)有AJAX同源策略,相當(dāng)危險(xiǎn)拗窃,我們發(fā)起的每一次HTTP請(qǐng)求都會(huì)帶上請(qǐng)求地址對(duì)應(yīng)的cookie瞎领,那么可以做如下攻擊:用戶登錄了自己的銀行頁(yè)面 http://mybank.com泌辫,http://mybank.com向用戶的cookie中添加用戶標(biāo)識(shí)。用戶瀏覽了惡意頁(yè)面 http://evil.com九默。執(zhí)行了頁(yè)面中的惡意AJAX請(qǐng)求代碼震放。http://evil.com向http://mybank.com發(fā)起AJAX HTTP請(qǐng)求,請(qǐng)求會(huì)默認(rèn)把http://mybank.com對(duì)應(yīng)cookie也同時(shí)發(fā)送過(guò)去驼修。銀行頁(yè)面從發(fā)送的cookie中提取用戶標(biāo)識(shí)殿遂,驗(yàn)證用戶無(wú)誤,response中返回請(qǐng)求數(shù)據(jù)乙各。此時(shí)數(shù)據(jù)就泄露了墨礁。而且由于Ajax在后臺(tái)執(zhí)行,用戶無(wú)法感知這一過(guò)程觅丰。
DOM同源策略也一樣饵溅,如果iframe之間可以跨域訪問(wèn),可以這樣攻擊:做一個(gè)假網(wǎng)站妇萄,里面用iframe嵌套一個(gè)銀行網(wǎng)站 http://mybank.com蜕企。把iframe寬高啥的調(diào)整到頁(yè)面全部,這樣用戶進(jìn)來(lái)除了域名冠句,別的部分和銀行的網(wǎng)站沒(méi)有任何差別轻掩。這時(shí)如果用戶輸入賬號(hào)密碼,我們的主網(wǎng)站可以跨域訪問(wèn)到http://mybank.com的dom節(jié)點(diǎn)懦底,就可以拿到用戶的輸入了唇牧,那么就完成了一次攻擊。
跨域的解決方式
解決方案
document.domain
window.name
jsonp
postMessage
cors
document.domain
-
關(guān)鍵點(diǎn)
跨域分為兩種聚唐,一種xhr不能訪問(wèn)不同源的文檔丐重,另一種是不同window之間不能進(jìn)行交互操作;
document.domain主要是解決第二種情況,且只能適用于主域相同子域不同的情況杆查;
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)不相同了等恐。 - 兼容性:所有瀏覽器都支持;
-
優(yōu)點(diǎn):
可以實(shí)現(xiàn)不同window之間的相互訪問(wèn)和操作; -
缺點(diǎn):
只適用于父子window之間的通信鼠锈,不能用于xhr闪檬;
只能在主域相同且子域不同的情況下使用星著; -
使用方式
a(當(dāng)前頁(yè)面或父頁(yè)面)頁(yè)面中加入document.domain = ‘example.com’;
b(當(dāng)前頁(yè)面或子頁(yè)面)頁(yè)面中加入document.domain = ‘example.com’;
a頁(yè)面訪問(wèn)b頁(yè)面里面的數(shù)據(jù)或者方法购笆; -
Example
有一個(gè)頁(yè)面,它的地址是http://www.example.com/a.html 虚循, 在這個(gè)頁(yè)面里面有一個(gè)iframe同欠,它的src是http://example.com/b.html
1.在頁(yè)面 http://www.example.com/a.html 中設(shè)置document.domain:
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'example.com';//設(shè)置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對(duì)象
}
</script>
2.在頁(yè)面 http://example.com/b.html 中也設(shè)置document.domain:
<script type="text/javascript">
document.domain = 'example.com';//在iframe載入這個(gè)頁(yè)面也設(shè)置document.domain,使之與主頁(yè)面的document.domain相同
</script>
window.name
關(guān)鍵點(diǎn)
window.name在頁(yè)面的生命周期里共享一個(gè)window.name;
兼容性
所有瀏覽器都支持横缔;
優(yōu)點(diǎn)
最簡(jiǎn)單的利用了瀏覽器的特性來(lái)做到不同域之間的數(shù)據(jù)傳遞铺遂;
不需要前端和后端的特殊配制;缺點(diǎn)
大小限制:window.name最大size是2M左右茎刚,不同瀏覽器中會(huì)有不同約定襟锐;
安全性:當(dāng)前頁(yè)面所有window都可以修改,很不安全膛锭;
數(shù)據(jù)類型:傳遞數(shù)據(jù)只能限于字符串粮坞,如果是對(duì)象或者其他會(huì)自動(dòng)被轉(zhuǎn)化為字符串,如下初狰;
window.name非字符串測(cè)試
使用方式:修改window.name的值即可莫杈;Example
a.html中代碼:
<script>
window.name = '我是頁(yè)面a中設(shè)置的值';
setInterval(function(){
window.location = 'b.html';
},2000)//兩秒后把一個(gè)新頁(yè)面b.html載入到當(dāng)前的window中
</script>
b.html中的代碼
<script>
console.log(window.name);//讀取window.name的值
</script>
jsonp
關(guān)鍵點(diǎn)
瀏覽器對(duì)XHR做了同源策略,但并沒(méi)有將這種方式延續(xù)到script上(其實(shí)還有iframe奢入,img等)
從而可以利用動(dòng)態(tài)script標(biāo)簽技術(shù)來(lái)做到跨域請(qǐng)求的作用筝闹。至于為什么會(huì)這樣設(shè)計(jì),本人也不太清楚腥光,有可能是歷史遺跡(漏洞)关顷,有可能是某些方面的技術(shù)瓶頸,也有可能是為了滿足某些需求專門(mén)定制的武福,總之這項(xiàng)技術(shù)方案我們過(guò)去可以用议双,現(xiàn)在可以用就ok,至于將來(lái)應(yīng)該也是會(huì)存在的艘儒,畢竟現(xiàn)在已經(jīng)應(yīng)用在很多家站點(diǎn)上聋伦,就算會(huì)廢棄,也會(huì)有一段時(shí)間迭代界睁。
在js中觉增,我們直接用XMLHttpRequest請(qǐng)求不同域上的數(shù)據(jù)時(shí),是不可以的翻斟。但是逾礁,在頁(yè)面上引入不同域上的js腳本文件卻是可以的,jsonp正是利用這個(gè)特性來(lái)實(shí)現(xiàn)的。兼容性
所有瀏覽器都兼容這種方式嘹履;優(yōu)點(diǎn)
很明顯前端可以很輕松的做到跨域請(qǐng)求腻扇;缺點(diǎn)
只能通過(guò)GET方式請(qǐng)求,一方面是參數(shù)長(zhǎng)度有限制砾嫉,二是安全性比較差幼苛;
后端需要知道前端的cb是什么樣的結(jié)構(gòu),主要在參數(shù)和回調(diào)名焕刮;
后端需要進(jìn)行參數(shù)和cb的拼接然后才能執(zhí)行舶沿;
<script type="text/javascript">
function dosomething(jsondata){
//處理獲得的json數(shù)據(jù)
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>
js文件載入成功后會(huì)執(zhí)行我們?cè)趗rl參數(shù)中指定的函數(shù)拟赊,并且會(huì)把我們需要的json數(shù)據(jù)作為參數(shù)傳入勋拟。所以jsonp是需要服務(wù)器端的頁(yè)面進(jìn)行相應(yīng)的配合的。
<?php
$callback = $_GET['callback'];//得到回調(diào)函數(shù)名
$data = array('a','b','c');//要返回的數(shù)據(jù)
echo $callback.'('.json_encode($data).')';//輸出
?>
最終提揍,輸出結(jié)果為:dosomething(['a','b','c']);
postMessage
關(guān)鍵點(diǎn)
window.postMessage(message,targetOrigin) 方法是html5新引進(jìn)的特性溉旋,可以使用它來(lái)向其它的window對(duì)象發(fā)送消息畸冲,無(wú)論這個(gè)window對(duì)象是屬于同源或不同源,目前IE8+观腊、FireFox邑闲、Chrome、Opera等瀏覽器都已經(jīng)支持window.postMessage方法恕沫。兼容性
移動(dòng)端可以放心用监憎,但是pc端需要做降級(jí)處理,具體可以根據(jù)文中介紹的這幾種跨域方式來(lái)則情選擇婶溯;優(yōu)點(diǎn)
不需要后端介入就可以非常簡(jiǎn)單的的做到跨域鲸阔,一個(gè)函數(shù)外加兩個(gè)參數(shù)(請(qǐng)求url,發(fā)送數(shù)據(jù))就可以搞定迄委;
移動(dòng)端兼容性好褐筛;缺點(diǎn)
無(wú)法做到一對(duì)一的傳遞方式:監(jiān)聽(tīng)中需要做很多消息的識(shí)別,由于postMessage發(fā)出的消息對(duì)于同一個(gè)頁(yè)面的不同功能相當(dāng)于一個(gè)廣播的過(guò)程叙身,該頁(yè)面的所有onmessage都會(huì)收到渔扎,所以需要做消息的判斷;
安全性問(wèn)題:三方可以通過(guò)截獲信轿,注入html或者腳本的形式監(jiān)聽(tīng)到消息晃痴,從而能夠做到篡改的效果,所以在postMessage和onmessage中一定要做好這方面的限制财忽;
發(fā)送的數(shù)據(jù)會(huì)通過(guò)結(jié)構(gòu)化克隆算法進(jìn)行序列化倘核,所以只有滿足該算法要求的參數(shù)才能夠被解析,否則會(huì)報(bào)錯(cuò)即彪,如function就不能當(dāng)作參數(shù)進(jìn)行傳遞紧唱;例子
otherWindow.postMessage(message, targetOrigin);
otherWindow: 對(duì)接收信息頁(yè)面的window的引用。可以是頁(yè)面中iframe的contentWindow屬性漏益;window.open的返回值蛹锰;通過(guò)name或下標(biāo)從window.frames取到的值。
message: 所要發(fā)送的數(shù)據(jù)绰疤,string類型铜犬。
targetOrigin: 用于限制otherWindow,“*”表示不作限制Example
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');
// 若寫(xiě)成'http://b.com/c/proxy.html'效果一樣
// 若寫(xiě)成'http://c.com'就不會(huì)執(zhí)行postMessage了
var targetOrigin = 'http://b.com';
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
b.com/index.html中的代碼:
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通過(guò)origin屬性判斷消息來(lái)源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 彈出"I was there!"
alert(event.source); // 對(duì)a.com峦睡、index.html中window對(duì)象的引用
// 但由于同源策略翎苫,這里event.source不可以訪問(wèn)window對(duì)象
}
}, false);
</script>
cors(Cross-origin resource sharing)
- 關(guān)鍵點(diǎn)
- cors是一種通過(guò)前后端http header配置來(lái)進(jìn)行跨域的一種方式;
兼容性:如果不考慮pc端的IE榨了,移動(dòng)端的opera的話那兼容性還是不錯(cuò)的,針對(duì)ie和opera可以做適當(dāng)?shù)慕导?jí)處理攘蔽;
poseMessage兼容性
安全策略
請(qǐng)求
origin:通過(guò)http頭中的origin判斷域名是否是允許的龙屉;
Example-Same-origin:如果http origin不存在,最好能夠自己在請(qǐng)求頭中加入該參數(shù)來(lái)標(biāo)示是否是同源满俗,true表示請(qǐng)求來(lái)自于同域名下(同域名下請(qǐng)求不帶origin)转捕;如果該字段存在并且為true則允許請(qǐng)求接口,否則禁止唆垃;
Example_source_origin:該參數(shù)同origin五芝,是在origin不存在的情況下用來(lái)標(biāo)示請(qǐng)求來(lái)源的url;
返回
Access-Control-Allow-Origin: origin辕万,origin表示允許哪些網(wǎng)站請(qǐng)求枢步,不建議設(shè)置為*;
Access-Control-Expose-Headers:Example-Access-Control-Allow-Source-Origin渐尿,允許http返回中包含該字段醉途,可以通過(guò)這種方式在返回頭中加入自定義字段,如該例子中的Example-Access-Control-Allow-Source-Origin; -
優(yōu)點(diǎn)
前端方便不少砖茸,只需要發(fā)請(qǐng)求而不用考慮跨域問(wèn)題隘擎;
安全性能夠得以控制和保障; -
缺點(diǎn)
兼容性不全面凉夯,需要做降級(jí)處理货葬; -
Example
對(duì)于客戶端,我們還是正常使用xhr對(duì)象發(fā)送ajax請(qǐng)求劲够。唯一需要注意的是震桶,我們需要設(shè)置我們的xhr屬性withCredentials為true,不然的話再沧,cookie是帶不過(guò)去的哦尼夺,設(shè)置: xhr.withCredentials = true;對(duì)于服務(wù)器端,需要在 response header中設(shè)置如下兩個(gè)字段:
Access-Control-Allow-Origin: http://www.yourhost.com
Access-Control-Allow-Credentials:true
這樣,我們就可以跨域請(qǐng)求接口了淤堵。