同域限制和window.postMessage方法

2021-09-15 15:17 更新

概述

所謂“同域限制”指的是,出于安全考慮,瀏覽器只允許腳本與同樣協(xié)議、同樣端口、同樣域名的地址進(jìn)行通信。比如,www1.example.com頁面上面的腳本,只能與該域名(相同協(xié)議、相同端口)進(jìn)行通信,如果與www2.example.com通信,瀏覽器就會報錯(不過可以設(shè)置兩者的document.domain為相同的值)。這是為了防止惡意腳本將用戶信息發(fā)往第三方網(wǎng)站。

window.postMessage方法就是用來在某種程度上,繞過同域限制,實現(xiàn)不同域名的窗口(包括iframe窗口)之間的通信。它的格式如下。

targetWindow.postMessage(message, targetURL[, transferObject]);

上面代碼的targetWindow是指向目標(biāo)窗口的變量,message是要發(fā)送的信息,targetURL是指定目標(biāo)窗口的網(wǎng)址,不符合該網(wǎng)址就不發(fā)送信息,transferObject則是跟隨信息一起發(fā)送的Transferable對象。

下面是一個postMessage方法的實例。假定當(dāng)前網(wǎng)頁彈出一個新窗口。

var popup = window.open(...popup details...);

popup.postMessage("Hello World!", "http://example.org");

上面代碼的postMessage方法的第一個參數(shù)是實際發(fā)送的信息,第二個參數(shù)是指定發(fā)送對象的域名必須是example.org。如果對方窗口不是這個域名,信息不會發(fā)送出去。

然后,在當(dāng)前網(wǎng)頁上監(jiān)聽message事件。

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event) {
    if (event.origin !== "http://example.org")
    return;

    if (event.data == 'Hello World') {
      event.source.postMessage('Hello', event.origin);
    } else {
      console.log(event.data);
    }

}

上面代碼指定message事件的回調(diào)函數(shù)為receiveMessage,一旦收到其他窗口發(fā)來的信息,receiveMessage函數(shù)就會被調(diào)用。receiveMessage函數(shù)接受一個event事件對象作為參數(shù),該對象的origin屬性表示信息的來源網(wǎng)址,如果該網(wǎng)址不符合要求,就立刻返回,不再進(jìn)行下一步處理。event.data屬性則包含了實際發(fā)送過來的信息,event.source屬性,指向當(dāng)前網(wǎng)頁發(fā)送信息的窗口對象。

最后,在popup窗口中部署下面的代碼。

// popup窗口

function receiveMessage(event) {
  event.source.postMessage("Nice to see you!", "*");
}

window.addEventListener("message", receiveMessage, false);

上面代碼有幾個地方需要注意。首先,receiveMessage函數(shù)里面沒有過濾信息的來源,任意網(wǎng)址發(fā)來的信息都會被處理。其次,postMessage方法中指定的目標(biāo)窗口的網(wǎng)址是一個星號,表示該信息可以向任意網(wǎng)址發(fā)送。通常來說,這兩種做法是不推薦的,因為不夠安全,可能會被惡意利用。

所有瀏覽器都支持這個方法,但是IE 8和IE 9只允許postMessage方法與iFrame窗口通信,不能與新窗口通信。IE 10允許與新窗口通信,但是只能使用IE特有的MessageChannel對象。

iframe與主頁面的通信

iframe中的網(wǎng)頁,如果與主頁面來自同一個域,通過設(shè)置document.domain屬性,可以使用postMessage方法實現(xiàn)雙向通信。

下面是一個LocalStorage的例子。LocalStorage只能用同一個域名的網(wǎng)頁讀寫,但是如果iframe是主頁面的子域名,主頁面就可以通過postMessage方法,讀寫iframe網(wǎng)頁設(shè)置的LocalStorage數(shù)據(jù)。

iframe頁面的代碼如下。

document.domain = "domain.com";
window.onmessage = function(e) {
  if (e.origin !== "http://domain.com") {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

主頁面的代碼如下。

window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    win.postMessage(JSON.stringify({key: 'storage', data: obj}), "*");
};

上面的代碼已經(jīng)可以實現(xiàn),主頁面向iframe傳入數(shù)據(jù)。如果還想讀取或刪除數(shù)據(jù),可以進(jìn)一步加強(qiáng)代碼。

加強(qiáng)版的iframe代碼如下。

document.domain = "domain.com";
window.onmessage = function(e) {
    if (e.origin !== "http://domain.com") {
        return;
    }
    var payload = JSON.parse(e.data);
    switch(payload.method) {
        case 'set':
            localStorage.setItem(payload.key, JSON.stringify(payload.data));
            break;
        case 'get':
            var parent = window.parent;
            var data = localStorage.getItem(payload.key);
            parent.postMessage(data, "*");
            break;
        case 'remove':
            localStorage.removeItem(payload.key);
            break;
    }
};

加強(qiáng)版的主頁面代碼如下。

window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    // 存入對象
    win.postMessage(JSON.stringify({key: 'storage', method: "set", data: obj}), "*");
    // 讀取以前存取的對象
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
    window.onmessage = function(e) {
        if (e.origin != "http://sub.domain.com") {
            return;
        }
        // 下面會輸出"Jack"
        console.log(JSON.parse(e.data).name);
    };
};

參考鏈接

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號