什么是同源策略
同源策略是一個(gè)重要的安全策略,它用于限制一個(gè)origin的文檔或者它加載的腳本如何能與另一個(gè)源的資源進(jìn)行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。
同源的定義
URL的組成

如果兩個(gè) URL 的 protocol、port (如果有指定的話)和 host 都相同的話,則這兩個(gè) URL 是同源。這個(gè)方案也被稱(chēng)為“協(xié)議/主機(jī)/端口元組”,或者直接是 “元組”。(“元組” 是指一組項(xiàng)目構(gòu)成的整體)
舉個(gè)例子:對(duì)于URL http://store.company.com/dir/page.html,同下面的URL進(jìn)行比較

源的繼成
在頁(yè)面中通過(guò) about:blank 或 javascript: URL 執(zhí)行的腳本會(huì)繼承打開(kāi)該 URL 的文檔的源,因?yàn)檫@些類(lèi)型的 URLs 沒(méi)有包含源服務(wù)器的相關(guān)信息
源的修改
滿足某些限制條件的情況下,頁(yè)面是可以修改它的源。腳本可以將 document.domain 的值設(shè)置為其當(dāng)前域或其當(dāng)前域的父域。如果將其設(shè)置為其當(dāng)前域的父域,則這個(gè)較短的父域?qū)⒂糜诤罄m(xù)源檢查。
例如:假設(shè) http://store.company.com/dir/other.html 文檔中的一個(gè)腳本執(zhí)行以下語(yǔ)句:
`document.domain = "company.com";
這條語(yǔ)句執(zhí)行之后,頁(yè)面將會(huì)成功地通過(guò)與 http://company.com/dir/page.html 的同源檢測(cè)(假設(shè)http://company.com/dir/page.html 將其 document.domain 設(shè)置為“company.com”,以表明它希望允許這樣做)。然而,company.com 不能設(shè)置 document.domain 為 othercompany.com,因?yàn)樗皇?company.com 的父域。
不同源的限制
Cookie、LocalStorage, 無(wú)法讀取。
DOM 無(wú)法獲得。
AJAX 請(qǐng)求不能發(fā)送。
跨站資源訪問(wèn)
Cookie
Cookie 是服務(wù)器寫(xiě)入瀏覽器的一小段信息,只有同源的網(wǎng)頁(yè)才能共享。但是,兩個(gè)網(wǎng)頁(yè)一級(jí)域名相同,只是二級(jí)域名不同,瀏覽器允許通過(guò)設(shè)置document.domain共享 Cookie
舉例來(lái)說(shuō),A網(wǎng)頁(yè)是http://w1.example.com/a.html,B網(wǎng)頁(yè)是http://w2.example.com/b.html,那么只要設(shè)置相同的document.domain,兩個(gè)網(wǎng)頁(yè)就可以共享Cookie。
document.domain = 'example.com';
現(xiàn)在,A網(wǎng)頁(yè)通過(guò)腳本設(shè)置一個(gè) Cookie。
document.cookie = "test1=hello";
B網(wǎng)頁(yè)就可以讀到這個(gè) Cookie。
var allCookie = document.cookie;
注意,這種方法只適用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 無(wú)法通過(guò)這種方法,規(guī)避同源政策,而要使用下文介紹的PostMessage API。
另外,服務(wù)器也可以在設(shè)置Cookie的時(shí)候,指定Cookie的所屬域名為一級(jí)域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
這樣的話,二級(jí)域名和三級(jí)域名不用做任何設(shè)置,都可以讀取這個(gè)Cookie。
Iframe
如果兩個(gè)網(wǎng)頁(yè)不同源,就無(wú)法拿到對(duì)方的DOM。典型的例子是iframe窗口和window.open方法打開(kāi)的窗口,它們與父窗口無(wú)法通信。
比如,父窗口運(yùn)行下面的命令,如果iframe窗口不是同源,就會(huì)報(bào)錯(cuò)。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想獲取子窗口的DOM,因?yàn)榭缭磳?dǎo)致報(bào)錯(cuò)。
反之亦然,子窗口獲取主窗口的DOM也會(huì)報(bào)錯(cuò)。
window.parent.document.body
// 報(bào)錯(cuò)
如果兩個(gè)窗口一級(jí)域名相同,只是二級(jí)域名不同,那么設(shè)置上一節(jié)介紹的document.domain屬性,就可以規(guī)避同源政策,拿到DOM。
對(duì)于完全不同源的網(wǎng)站,目前有兩種方法,可以解決跨域窗口的通信問(wèn)題。
- 片段識(shí)別符(fragment identifier)
- 跨文檔通信API(Cross-document messaging)
片段識(shí)別符
片段標(biāo)識(shí)符(fragment identifier)指的是,URL的#號(hào)后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改變片段標(biāo)識(shí)符,頁(yè)面不會(huì)重新刷新。
父窗口可以把信息,寫(xiě)入子窗口的片段標(biāo)識(shí)符。
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
子窗口通過(guò)監(jiān)聽(tīng)hashchange事件得到通知。
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}
同樣的,子窗口也可以改變父窗口的片段標(biāo)識(shí)符。
parent.location.href= target + "#" + hash;
window.postMessage
HTML5為了解決這個(gè)問(wèn)題,引入了一個(gè)全新的API:跨文檔通信 API(Cross-document messaging)。
這個(gè)API為window對(duì)象新增了一個(gè)window.postMessage方法,允許跨窗口通信,不論這兩個(gè)窗口是否同源。
舉例來(lái)說(shuō),父窗口http://aaa.com向子窗口http://bbb.com發(fā)消息,調(diào)用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一個(gè)參數(shù)是具體的信息內(nèi)容,第二個(gè)參數(shù)是接收消息的窗口的源(origin),即"協(xié)議 + 域名 + 端口"。也可以設(shè)為*,表示不限制域名,向所有窗口發(fā)送。
子窗口向父窗口發(fā)送消息的寫(xiě)法類(lèi)似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通過(guò)message事件,監(jiān)聽(tīng)對(duì)方的消息。
window.addEventListener('message', function(e) {
console.log(e.data);
},false);
message事件的事件對(duì)象event,提供以下三個(gè)屬性。
*event.source:發(fā)送消息的窗口
*event.origin: 消息發(fā)向的網(wǎng)址
- event.data: 消息內(nèi)容
下面的例子是,子窗口通過(guò)event.source屬性引用父窗口,然后發(fā)送消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
event.origin屬性可以過(guò)濾不是發(fā)給本窗口的消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://aaa.com') return;
if (event.data === 'Hello World') {
event.source.postMessage('Hello', event.origin);
} else {
console.log(event.data);
}
}
AJAX
同源政策規(guī)定,AJAX請(qǐng)求只能發(fā)給同源的網(wǎng)址,否則就報(bào)錯(cuò)。
除了架設(shè)服務(wù)器代理(瀏覽器請(qǐng)求同源服務(wù)器,再由后者請(qǐng)求外部服務(wù)),有三種方法規(guī)避這個(gè)限制。
- JSONP
- WebSocket
- CORS