2020-05-06

js同源限制

所謂同源指的是“三個相同”

.協議相同

.域名相同

.端口相同

舉例來說,http://www.example.com/dir/page.html這個網址,協議是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:不同源(協議不同)

目的

同源政策的目的是為了保證用戶信息的安全。防止惡意的網站竊取數據
設想這樣一種情況:A 網站是一家銀行,用戶登錄以后,A 網站在用戶的機器上設置了一個 Cookie,包含了一些隱私信息(比如存款總額)。用戶離開 A 網站以后,又去訪問 B 網站,如果沒有同源限制,B 網站可以讀取 A 網站的 Cookie,那么隱私信息就會泄漏。更可怕的是,Cookie 往往用來保存用戶的登錄狀態(tài),如果用戶沒有退出登錄,其他網站就可以冒充用戶,為所欲為。因為瀏覽器同時還規(guī)定,提交表單不受同源政策的限制。

由此可見,同源政策是必需的,否則 Cookie 可以共享,互聯網就毫無安全可言了。

限制范圍

隨著互聯網的發(fā)展,同源政策越來越嚴格。目前,如果非同源,共有三種行為受到限制。

  1. 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB。
  2. 無法接觸非同源網頁的 DOM。
  3. 無法向非同源地址發(fā)送 AJAX 請求(可以發(fā)送,但瀏覽器會拒絕接收響應)

另外,通過 JavaScript 腳本可以拿到其他窗口的window對象。如果是非同源的網頁,目前允許一個窗口可以接觸其他網頁的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對象,非同源的情況下,也只允許調用location.replace方法和寫入location.href屬性。

雖然這些限制是必要的,但是有時很不方便,合理的用途也受到影響。下面介紹如何規(guī)避上面的限制。

cookie

Cookie 是服務器寫入瀏覽器的一小段信息,只有同源的網頁才能共享。如果兩個網頁一級域名相同,只是次級域名不同,瀏覽器允許通過設置document.domain共享 Cookie。

舉例來說,A 網頁的網址是http://w1.example.com/a.html,B 網頁的網址是http://w2.example.com/b.html,那么只要設置相同的document.domain,兩個網頁就可以共享 Cookie。因為瀏覽器通過document.domain屬性來檢查是否同源。

// 兩個網頁都需要設置
document.domain = 'example.com';

注意,A 和 B 兩個網頁都需要設置document.domain屬性,才能達到同源的目的。因為設置document.domain的同時,會把端口重置為null,因此如果只設置一個網頁的document.domain,會導致兩個網址的端口不同,還是達不到同源的目的。
現在,A 網頁通過腳本設置一個 Cookie。

document.cookie = "test1=hello";

B 網頁就可以讀到這個 Cookie。

var allCookie = document.cookie;

注意,這種方法只適用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexedDB 無法通過這種方法,規(guī)避同源政策,而要使用PostMessage API。

另外,服務器也可以在設置 Cookie 的時候,指定 Cookie 的所屬域名為一級域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/

這樣的話,二級域名和三級域名不用做任何設置,都可以讀取這個 Cookie。

iframe 和多窗口通信

iframe元素可以在當前網頁之中,嵌入其他網頁。每個iframe元素形成自己的窗口,即有自己的window對象。iframe窗口之中的腳本,可以獲得父窗口和子窗口。但是,只有在同源的情況下,父窗口和子窗口才能通信;如果跨域,就無法拿到對方的 DOM。

比如,父窗口運行下面的命令,如果iframe窗口不是同源,就會報錯。

document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.

上面命令中,父窗口想獲取子窗口的 DOM,因為跨域導致報錯。

反之亦然,子窗口獲取主窗口的 DOM 也會報錯。

window.parent.document.body // 報錯

這種情況不僅適用于iframe窗口,還適用于window.open方法打開的窗口,只要跨域,父窗口與子窗口之間就無法通信。

如果兩個窗口一級域名相同,只是二級域名不同,那么設置document.domain屬性,就可以規(guī)避同源政策,拿到 DOM。

對于完全不同源的網站,目前有兩種方法,可以解決跨域窗口的通信問題。

片段識別符(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ā)消息,調用postMessage方法就可以了。

// 父窗口打開一個子窗口
var popup = window.open('http://bbb.com', 'title');
// 父窗口向子窗口發(fā)消息
popup.postMessage('Hello World!', 'http://bbb.com');

postMessage方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源(origin),即“協議 + 域名 + 端口”。也可以設為*,表示不限制域名,向所有窗口發(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事件的參數是事件對象event,提供以下三個屬性。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容