父層打開了一個iframe,但要如何與它溝通?還有子層iframe又要如何傳訊息給將它嵌入的父層?PostMessage可以解決這個問題。
介紹:
父層佈署在AWS上,嵌入一個佈署在Github Pages的子層網址。利用PostMessage來溝通。(DEMO網址)
核心概念:
1.創建相對瀏覽器窗口對象(父層/子層)
2.透過postmessage對窗口對象發送訊息 / 透過EventListener監聽message事件,取得窗口對象訊息
父層
1.在HTML中建立要嵌入的iframe,並取名為iframeChild
<iframe name="iframeChild"></iframe>
2.在TS中透過window.open取得子層窗口對象,透過剛剛建立的iframe打開
this.iframeChild = window.open('https://1mid.github.io/childIframe/', 'iframeChild');
3.透過子層窗口對象的postmessage方法對子層發送訊息
this.iframeChild?.postMessage({ type: 'iframeChannel', message: msg }, '*');
最後一個參數為targetOrigin,設’*’代表不檢查。在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那麼消息就不會被發送。可以避免被爛用
4.監聽message事件,監聽來自子層的訊息事件
@HostListener('window:message', ['$event'])onMessage(event: any) { if (event.data?.type === "iframeChannel") { this.msg += 'child:' + JSON.stringify(event.data.message) + '\n'; }}
event.data.type是為了用來區分開別的應用的message,否則會聽到其他應用的訊息(ex.webpack)。
子層
1.取得父層的窗口對象
iframeParent: Window | undefined;this.iframeParent = window.opener as Window;
2.透過父層窗口對象對父層發送訊息
this.iframeParent?.postMessage({ type: 'iframeChannel', message: text }, '*');
3.監聽message事件,監聽來自父層的訊息事件
@HostListener('window:message', ['$event'])onMessage(event: any) { if (event.data?.type === "iframeChannel") { this.msg += 'parent:' + JSON.stringify(event.data.message) + '\n'; }}
補充
ios手機不觸發onLoad事件(2023/11/20)
<iframe onload = "onload()"></iframe>
目前觀察到若是非同源的網站互相嵌入,手機系統是ios開啟網站時,有放置iframe的頁面onLoad事件不會被觸發(但是Android可以觸發)。
解決方法是透過被嵌入的網站以postMessage的方式告知主頁已經init完畢。
(這問題真的很搞)
實作測試
iframe Child已經建好了,可以被任何父層透過window.open的方式嵌入,所以可以嘗試自己建一個iframe Parent用window.open的方式打開https://1mid.github.io/childIframe/,測試看看是否能正常溝通。
iframe要如何拿到父層的地址?
var referrer = document.referrer; //取得route過來的前地址(MDN)
iframe的限制
若子層的iframe有使用session,則這個session並不會存在iframe,而是會存在父層,這時如果子層需要取得session的時候就會拿不到,是因為瀏覽器安全性政策的關係,被嵌入iframe無法取得父層的session,避免CSRF。
如果iframe可以取得session會發生什麼事?
就可以透過放一個隱藏的iframe,並讓程式自己去submit,這樣就能偽造該使用者的身分,去執行一些操作。
<iframe style="display:none" name="csrf-frame"></iframe> <form method='POST' action='https://small-min.blog.com/delete' target="csrf-frame" id="csrf-form">
<input type='hidden' name='id' value='3'>
<input type='submit' value='submit'>
</form> <script>document.getElementById("csrf-form").submit()</script>
參考:讓我們來談談 CSRF