iframe實現與父頁面跨域隔離的JavaScript 代碼沙箱
目錄
- 正文
- 1. iframe
- 2. data URL
- 3. 將 JavaScript 代碼變成 data URL
- 4. 如果需要獲取執行結果的話,基于 postMessage 定制通信機制
正文
假如讓你實現一個在線的 JavaScript 代碼運行環境,要求用戶代碼不能對頁面進行修改,以避免潛在的安全問題,你會怎么做?
使用 with
?使用 proxy
?OK ,都可以,但是這兩種方法都需要關注很多細節,否則用戶依舊有可乘之機,這樣一來你的實現里面就會有一個很長長長長長長長的操作黑名單。
除此之外,我們還可以專門部署一個頁面,將代碼提到服務端渲染成頁面,再通過 iframe 去訪問,如果 iframe 與父頁面之間是跨域的話可以達到很高的安全性——那么能不能不看后端的臉色,完全使用瀏覽器來實現類似的沙箱呢?
當然可以——
1. iframe
對前端頁面而言,跨域是頁面與頁面之間的鴻溝,但這并不意味著我們必須重新打開一個頁面來運行新的代碼,因為我們可以使用 <iframe>
標簽:
<iframe src="www.xxxx.xxx"></iframe>
對于同域的 iframe ,我們可以直接通過 .contentWindow
訪問并操作它的全局對象,然后直接往里面執行 JavaScript:
document.querySelector("iframe") .contentWindow .eval("alert("hello world!");");
但是同域頁面的子頁面是可以與父頁面進行互操作的,
2. data URL
你可能在一些頁面里見過小圖片不使用網絡鏈接,而是采用一個 data:image/png;base64 xxxxxxx
風格的 URL ,這種 URL 就是 data URL。
除了 data URL 之外你可能還見過 blob://
開頭的 URL —— Object URL。不過 Object URL 與當前頁面是同域的,而 data URL 與當前頁面是跨域的。所以我們可以在 iframe 使用 data URL 來進行跨域隔離。
3. 將 JavaScript 代碼變成 data URL
我們可以直接將 JavaScript 片段變成 data:application/javascript,
的 URL ,但是這樣有一個問題: iframe 打開這樣的 URL 的時候,會顯示代碼原文而不是執行代碼,這個行為其實和你直接在瀏覽器地址欄輸入 JS 的 URL 是一樣的。
所以我們需要將 JavaScript 代碼拼接到 html
里面,再變成 data URL ,然后交給 iframe 去加載:
const javaScriptFragment = `alert("hello world");`;const htmlFragment = `<!doctype html><html> <head> <meta chatset="utf-8" /> </head> <body> <script>${javaScriptFragment}</script> </body></html>`;const dataUrl = `data:text/html,${htmlFragment}`;// 注意,如果代碼片段中含有中文的話,需要使用 encodeURIFragment 轉義 htmlFragmentdocument.querySelector("iframe").src = dataUrl;
4. 如果需要獲取執行結果的話,基于 postMessage 定制通信機制
如果我們不但要做沙箱隔離,還被要求獲取運行結果的話,則可以做一個通信機制,讓 iframe 獲取到用戶代碼執行結果, iframe 與父頁面之間最好的跨域通信方法莫過于 postMessage
。
除了獲取結果之外,還可以將通信機制進一步擴展成為 RPC ,這樣可以實時修改頁面里的代碼來查看效果,類似于 codepen 。
具體實現與主題不是強相關,這里就不寫了,更多關于JavaScript iframe頁面跨域隔離的資料請關注其它相關文章!