js同源限制
所謂同源指的是“三個相同”
.協(xié)議相同
.域名相同
.端口相同
舉例來說抒钱,http://www.example.com/dir/page.html
這個網(wǎng)址悲幅,協(xié)議是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:不同源(協(xié)議不同)
目的
同源政策的目的是為了保證用戶信息的安全飞蹂。防止惡意的網(wǎng)站竊取數(shù)據(jù)
設(shè)想這樣一種情況:A 網(wǎng)站是一家銀行几苍,用戶登錄以后,A 網(wǎng)站在用戶的機器上設(shè)置了一個 Cookie陈哑,包含了一些隱私信息(比如存款總額)擦剑。用戶離開 A 網(wǎng)站以后,又去訪問 B 網(wǎng)站芥颈,如果沒有同源限制,B 網(wǎng)站可以讀取 A 網(wǎng)站的 Cookie赚抡,那么隱私信息就會泄漏爬坑。更可怕的是,Cookie 往往用來保存用戶的登錄狀態(tài)涂臣,如果用戶沒有退出登錄盾计,其他網(wǎng)站就可以冒充用戶售担,為所欲為。因為瀏覽器同時還規(guī)定署辉,提交表單不受同源政策的限制族铆。
由此可見,同源政策是必需的哭尝,否則 Cookie 可以共享哥攘,互聯(lián)網(wǎng)就毫無安全可言了。
限制范圍
隨著互聯(lián)網(wǎng)的發(fā)展材鹦,同源政策越來越嚴格逝淹。目前,如果非同源桶唐,共有三種行為受到限制栅葡。
- 無法讀取非同源網(wǎng)頁的 Cookie、LocalStorage 和 IndexedDB尤泽。
- 無法接觸非同源網(wǎng)頁的 DOM欣簇。
- 無法向非同源地址發(fā)送 AJAX 請求(可以發(fā)送,但瀏覽器會拒絕接收響應(yīng))
另外坯约,通過 JavaScript 腳本可以拿到其他窗口的window對象熊咽。如果是非同源的網(wǎng)頁,目前允許一個窗口可以接觸其他網(wǎng)頁的window對象的九個屬性和四個方法:window.closed鬼店、window.frames网棍、window.length、window.location妇智、window.opener滥玷、window.parent、window.self巍棱、window.top惑畴、window.window、window.blur()航徙、window.close()如贷、window.focus()、window.postMessage()到踏。
上面的九個屬性之中杠袱,只有window.location
是可讀寫的,其他八個全部都是只讀窝稿。而且楣富,即使是location對象,非同源的情況下伴榔,也只允許調(diào)用location.replace
方法和寫入location.href
屬性纹蝴。
雖然這些限制是必要的庄萎,但是有時很不方便,合理的用途也受到影響塘安。下面介紹如何規(guī)避上面的限制糠涛。
cookie
Cookie
是服務(wù)器寫入瀏覽器的一小段信息,只有同源的網(wǎng)頁才能共享兼犯。如果兩個網(wǎng)頁一級域名相同忍捡,只是次級域名不同,瀏覽器允許通過設(shè)置document.domain
共享 Cookie免都。
舉例來說锉罐,A 網(wǎng)頁的網(wǎng)址是http://w1.example.com/a.html,B 網(wǎng)頁的網(wǎng)址是http://w2.example.com/b.html绕娘,那么只要設(shè)置相同的document.domain脓规,兩個網(wǎng)頁就可以共享 Cookie。因為瀏覽器通過document.domain屬性來檢查是否同源险领。
// 兩個網(wǎng)頁都需要設(shè)置
document.domain = 'example.com';
注意侨舆,A 和 B 兩個網(wǎng)頁都需要設(shè)置document.domain屬性,才能達到同源的目的绢陌。因為設(shè)置document.domain的同時挨下,會把端口重置為null,因此如果只設(shè)置一個網(wǎng)頁的document.domain脐湾,會導(dǎo)致兩個網(wǎng)址的端口不同臭笆,還是達不到同源的目的。
現(xiàn)在秤掌,A 網(wǎng)頁通過腳本設(shè)置一個 Cookie愁铺。
document.cookie = "test1=hello";
B 網(wǎng)頁就可以讀到這個 Cookie。
var allCookie = document.cookie;
注意闻鉴,這種方法只適用于 Cookie 和 iframe 窗口钦购,LocalStorage 和 IndexedDB 無法通過這種方法刹枉,規(guī)避同源政策,而要使用PostMessage API立轧。
另外卷雕,服務(wù)器也可以在設(shè)置 Cookie 的時候注簿,指定 Cookie 的所屬域名為一級域名敢茁,比如.example.com篡帕。
Set-Cookie: key=value; domain=.example.com; path=/
這樣的話,二級域名和三級域名不用做任何設(shè)置次询,都可以讀取這個 Cookie腋舌。
iframe 和多窗口通信
iframe
元素可以在當(dāng)前網(wǎng)頁之中,嵌入其他網(wǎng)頁渗蟹。每個iframe
元素形成自己的窗口块饺,即有自己的window
對象。iframe
窗口之中的腳本雌芽,可以獲得父窗口和子窗口授艰。但是,只有在同源的情況下世落,父窗口和子窗口才能通信淮腾;如果跨域,就無法拿到對方的 DOM
屉佳。
比如谷朝,父窗口運行下面的命令,如果iframe
窗口不是同源武花,就會報錯圆凰。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想獲取子窗口的 DOM体箕,因為跨域?qū)е聢箦e专钉。
反之亦然,子窗口獲取主窗口的 DOM 也會報錯累铅。
window.parent.document.body // 報錯
這種情況不僅適用于iframe窗口跃须,還適用于window.open方法打開的窗口,只要跨域娃兽,父窗口與子窗口之間就無法通信菇民。
如果兩個窗口一級域名相同,只是二級域名不同投储,那么設(shè)置document.domain屬性第练,就可以規(guī)避同源政策,拿到 DOM轻要。
對于完全不同源的網(wǎng)站复旬,目前有兩種方法,可以解決跨域窗口的通信問題冲泥。
片段識別符(fragment identifier)
跨文檔通信API(Cross-document messaging)
片段標識符指的是驹碍,URL 的#號后面的部分,比如http://example.com/x.html#fragment的#fragment凡恍。如果只是改變片段標識符志秃,頁面不會重新刷新。
父窗口可以把信息嚼酝,寫入子窗口的片段標識符浮还。
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
上面代碼中,父窗口把所要傳遞的信息闽巩,寫入iframe窗口的片段標識符钧舌。
子窗口通過監(jiān)聽hashchange事件得到通知担汤。
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}
同樣的,子窗口也可以改變父窗口的片段標識符洼冻。
parent.location.href = target + '#' + hash;
window.postMessage()
上面的這種方法屬于破解崭歧,HTML5 為了解決這個問題,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)撞牢。
這個 API 為window對象新增了一個window.postMessage方法率碾,允許跨窗口通信,不論這兩個窗口是否同源屋彪。舉例來說所宰,父窗口aaa.com向子窗口bbb.com發(fā)消息,調(diào)用postMessage方法就可以了畜挥。
// 父窗口打開一個子窗口
var popup = window.open('http://bbb.com', 'title');
// 父窗口向子窗口發(fā)消息
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage
方法的第一個參數(shù)是具體的信息內(nèi)容仔粥,第二個參數(shù)是接收消息的窗口的源(origin),即“協(xié)議 + 域名 + 端口”砰嘁。也可以設(shè)為*件炉,表示不限制域名,向所有窗口發(fā)送矮湘。
子窗口向父窗口發(fā)送消息的寫法類似斟冕。
// 子窗口向父窗口發(fā)消息
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通過message事件,監(jiān)聽對方的消息缅阳。
// 父窗口和子窗口都可以用下面的代碼磕蛇,
// 監(jiān)聽 message 消息
window.addEventListener('message', function (e) {
console.log(e.data);
},false);
message事件的參數(shù)是事件對象event,提供以下三個屬性十办。