MDN之Window(三)【window.postMessage】

風靈使發表於2018-06-04
Web 開發技術 >Web API 介面參考> Window> window.postMessage

window.postMessage() 方法可以安全地實現跨源通訊。通常,對於兩個不同頁面的指令碼,只有當執行它們的頁面位於具有相同的協議(通常為https),埠號(443為https的預設值),以及主機  (兩個頁面的模數 Document.domain 設定為相同的值) 時,這兩個指令碼才能相互通訊。window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。

window.postMessage() 方法被呼叫時,會在所有頁面指令碼執行完畢之後(e.g., 在該方法之後設定的事件、之前設定的timeout 事件,etc.)向目標視窗派發一個   MessageEvent 訊息。 該MessageEvent 訊息有四個屬性需要注意: message 屬性表示該message 的型別; data 屬性為 window.postMessage 的第一個引數;origin 屬性表示呼叫window.postMessage() 方法時呼叫頁面的當前狀態; source 屬性記錄呼叫 window.postMessage() 方法的視窗資訊。

語法

otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
其他視窗的一個引用,比如iframe的contentWindow屬性、執行window.open返回的視窗物件、或者是命名過或數值索引的window.frames
message
將要傳送到其他 window的資料。它將會被結構化克隆演算法序列化。這意味著你可以不受什麼限制的將資料物件安全的傳送給目標視窗而無需自己序列化。[1]
targetOrigin
通過視窗的origin屬性來指定哪些視窗能接收到訊息事件,其值可以是字串"*"(表示無限制)或者一個URI。在傳送訊息的時候,如果目標視窗的協議、主機地址或埠這三者的任意一項不匹配targetOrigin提供的值,那麼訊息就不會被髮送;只有三者完全匹配,訊息才會被髮送。這個機制用來控制訊息可以傳送到哪些視窗;例如,當用postMessage傳送密碼時,這個引數就顯得尤為重要,必須保證它的值與這條包含密碼的資訊的預期接受者的origin屬性完全一致,來防止密碼被惡意的第三方截獲。如果你明確的知道訊息應該傳送到哪個視窗,那麼請始終提供一個有確切值的targetOrigin,而不是*。不提供確切的目標將導致資料洩露到任何對資料感興趣的惡意站點。
transfer 
是一串和message 同時傳遞的 Transferable 物件. 這些物件的所有權將被轉移給訊息的接收方,而傳送一方將不再保有所有權。

The dispatched event

執行如下程式碼, 其他window可以監聽派遣的message:

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

function receiveMessage(event)
{
<code>  // For Chrome, the origin property is in the event.originalEvent
  // object.
&nbsp; var origin = event.origin || event.originalEvent.origin; </code>
  if (origin&nbsp;!== "http://example.org:8080")
    return;

  // ...
}

 message 的屬性有:

data
從其他 window 中傳遞過來的物件。
origin
呼叫 postMessage  時訊息傳送方視窗的 origin . 這個字串由 協議、“://“、域名、“ : 埠號”拼接而成。例如 “https://example.org (隱含埠 443)”、“http://example.net (隱含埠 80)”、“http://example.com:8080”。請注意,這個origin不能保證是該視窗的當前或未來origin,因為postMessage被呼叫後可能被導航到不同的位置。
source
對傳送訊息的視窗物件的引用; 您可以使用此來在具有不同origin的兩個視窗之間建立雙向通訊。

安全問題

如果您不希望從其他網站接收message,請不要為message事件新增任何事件偵聽器。 這是一個完全萬無一失的方式來避免安全問題。

如果您確實希望從其他網站接收message,請始終使用origin和source屬性驗證發件人的身份。 任何視窗(包括例如http://evil.example.com)都可以向任何其他視窗傳送訊息,並且您不能保證未知發件人不會傳送惡意訊息。 但是,驗證身份後,您仍然應該始終驗證接收到的訊息的語法。 否則,您信任只傳送受信任郵件的網站中的安全漏洞可能會在您的網站中開啟跨網站指令碼漏洞。

當您使用postMessage將資料傳送到其他視窗時,始終指定精確的目標origin,而不是*。 惡意網站可以在您不知情的情況下更改視窗的位置,因此它可以攔截使用postMessage傳送的資料。

示例

/*
 * A視窗的域名是<http://example.com:8080>,以下是A視窗的script標籤下的程式碼:
 */

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

// 如果彈出框沒有被阻止且載入完成

// 這行語句沒有傳送資訊出去,即使假設當前頁面沒有改變location(因為targetOrigin設定不對)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假設當前頁面沒有改變location,這條語句會成功新增message到傳送佇列中去(targetOrigin設定對了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我們能相信資訊的傳送者嗎?  (也許這個傳送者和我們最初開啟的不是同一個頁面).
  if (event.origin&nbsp;!== "http://example.org")
    return;

  // event.source 是我們通過window.open開啟的彈出頁面 popup
  // event.data 是 popup傳送給當前頁面的訊息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);

/*
 * 彈出頁 popup 域名是<http://example.org>,以下是script標籤中的程式碼:
 */

//當A頁面postMessage被呼叫後,這個function被addEventListenner呼叫
function receiveMessage(event)
{
  // 我們能信任資訊來源嗎?
  if (event.origin&nbsp;!== "http://example.com:8080")
    return;

  // event.source 就當前彈出頁的來源頁面
  // event.data 是 "hello there!"

  // 假設你已經驗證了所受到資訊的origin (任何時候你都應該這樣做), 一個很方便的方式就是把enent.source
  // 作為回信的物件,並且把event.origin作為targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

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

注意

任何視窗可以在任何其他視窗訪問此方法,在任何時間,無論文件在視窗中的位置,向其傳送訊息。 因此,用於接收訊息的任何事件監聽器必須首先使用origin和source屬性來檢查訊息的傳送者的身份。 這不能低估:無法檢查origin和source屬性會導致跨站點指令碼攻擊。

與任何非同步排程的指令碼(超時,使用者生成的事件)一樣,postMessage的呼叫者不可能檢測到偵聽由postMessage傳送的事件的事件處理程式何時丟擲異常。

分派事件的origin屬性的值不受呼叫視窗中document.domain的當前值的影響。

僅對於IDN主機名,origin屬性的值不是始終為Unicode或punycode; 在使用此屬性時,如果您期望來自IDN網站的訊息,則最大程度地相容性檢查IDN和punycode值。 這個值最終將始終是IDN,但現在你應該同時處理IDN和punycode表單。

當傳送視窗包含javascript:或data:URL時,origin屬性的值是載入URL的指令碼的

在擴充套件中使用window.postMessage

window.postMessage可用於以chrome程式碼執行的JavaScript(例如,在擴充套件和特權程式碼中),但是分派事件的source屬性總是為空作為安全限制。 (其他屬性具有其期望值。)傳送到位於chrome:URL的視窗的訊息的targetOrigin引數當前被錯誤解釋,使得將導致傳送訊息的唯一值為“*”。 由於此值是不安全的,當目標視窗可以導航到其他地方的惡意網站,建議postMessage不用於與chrome:頁面的溝通; 使用不同的方法(如開啟視窗時的查詢字串)與chrome視窗進行通訊。 最後,在檔案中向頁面釋出訊息:URL當前要求targetOrigin引數為“*” file://不能用作安全限制; 這個限制可能會在將來被修改。

相關文章