文章標題有點饒舌難懂,直接說我需求就清楚了。我想在員工入口網站(例如:portal.utopia.com)加入人事、行政、會計、電子表單等現成網站功能,這些應用程式各有自己的網站(例如:webap.utopia.com),最簡單的整合方法是在入口網站放個Iframe將其他網站的網頁內嵌進來,兩分鐘搞定,用膝蓋就能完成。

BUT,人生最機X的就是這個BUT!

PM/老闆/使用者一定不會這麼簡單放過你,既然網頁已經整在一起,那麼切換樣式跟入口網站融為一體,審完表單入口網站的待審數字要減一,非常合情合理,應該難不倒你吧?不!瀏覽器跳出來說:「Over my dead body!」

母網頁跟Iframe網頁要溝通基本上不是難事,可用靠JavaScript操作另一方的DOM搞定,但若是兩個網頁分屬不同站台,問題就沒這麼單純。舉個實例,入口網站網頁httq://portal.utopia.com/SOP/container.aspx長這樣:

排版顯示純文字
<%@ Page Language="C#" %>
 
<!DOCTYPE html>
 
<html>
<head>
    <title></title>
    <style>
        iframe { width: 320px; height: 240px; margin: 12px; }
    </style>
</head>
<body>
    <h5></h5>
    <div>
        <button>Get value from IFrame</button>
    </div>
    <iframe id="frmEmbedded" src="http://webap.utopia.com/SOP/Frame.aspx"></iframe>
    <script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
    <script>
        $("h5").text(location.href);
        $("button").click(function () {
            alert($("#frmEmbedded").contents().find("#txtValue").val());
        });
    </script>
</body>
</html>

被嵌入的應用程式網頁httq://webap.utopia.com/SOP/embedded.aspx長這樣:

排版顯示純文字
<%@ Page Language="C#" %>
<!DOCTYPE html>
<html>
<head>
    <title>Frame</title>
    <style>
        body { 
            background-color: #ddd;
        }
    </style>
</head>
<body>
    <h5></h5>
    <input id="txtValue" value="32767" />
    <script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
    <script>
        $("h5").text(location.href);
    </script>
</body>
</html>

我們希望按下Container.aspx <button>時可以讀取Embedded.aspx的<input id="txtValue">並顯示其值,當Container.aspx與Embedded.aspx分屬不同主機(portal.utopia.com與webap.utopia.com),由Container.aspx存取Embedded.aspx的行為將被瀏覽器禁止:

出現以下錯誤:

Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "httq://portal.utopia.com" from accessing a frame with origin "httq://webap.utopia.com". Protocols, domains, and ports must match.

這個限制來自瀏覽器的同源政策(Single Origin Policy),是瀏覽器防止惡意程式作怪的基本安全防線,前端攻城獅必須摸清它的特性。關於SOP的細詳解說,我推薦阮一峰先生寫的文章:浏览器同源政策及其规避方法,是我目前看到最淺顯完整的中文文件。

我的案例發生在公司內部,符合後端域名相同條件,幸運地可以靠Container.aspx/Embedded.aspx同時加上document.domain="utopia.com"克服。

如此,藉由加註document.domain,入口網站與應用程式網站的網頁在彼此存取對方的DOM時,瀏覽器視為同網站的兩個網站應用程式,就不受同源政策限制囉~

【補充心得】

  1. document.domain="…"指定的內容必須與目前網址中的網域名稱相符。如果你在httq://portal.utopia.com的網頁中指定document.domain="darkthread.net",會出錯:Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'darkthread.net' is not a suffix of 'utopia.com'.
  2. 必須雙方配合才能成功,除了入口網站要加,也要協調被嵌入網頁的開發單位配合在網頁上加入document.domain設定。
  3. 關於document.domain的網名值,建議不要寫死成字串,讓.NET或JavaScript自動由目前的網址抓取是上策。
    C#可以使用以下語法:
        string.Join(".", Request.Url.Host.Split('.').Skip(1).ToArray())
    JavaScript則複雜一點是:
        /http(s)*:\/\/(.+?)\//i.exec(location.href)[2].split('.').slice(1).join(".")
        location.hostname.split('.').slice(1).join(".") (感謝Ammon大開示,location.hostname可直接取主機名稱)
  4. 要用這招,網址只能用網域名稱,不能用IP,故在公司進行內部測試時需配套措施:向DNS註冊測試主機,並限定使用者一律用網域名稱URL進行測試。

Comments

# by Jeffrey

Ammon大大一出手, 我又長知識了,大感謝!

# by ChrisTorng

在公司進行內部測試時應該可以使用 hosts 增加假的名稱/IP 對應,然後記得在瀏覽器中設定該假名稱不要經過 Proxy (因為 Proxy 中不會有我們設的假名/IP 對應),應該就可以測試了。以上我自己沒試過不確定可不可以。

# by Jeffrey

to ChrisTorng, 感謝分享,我們在開發人員測試階段也常用改hosts這招,快速簡便還能Side-By-Side每個人調成自己想要的組合。不過開放給用戶端測試人員時,還是會以註冊DNS為主,使用者不一定有能力或是有權限修改hosts,沒改好還會埋下刁鑽茶包,整體而言弊多於利。以上經驗供參。

# by BenWu

在Azure上試了一下,得到錯誤"Failed to set the 'domain' property on 'Document': 'azurewebsites.net' is a top-level domain"。 TWNIC上說「.com」、「.edu」、「.net」、「.org」等通用頂級網域名稱(Generic Top-Level Domain, gTLD), 看來還是有些限制。

# by Jeffrey

to BenWu, 像azurewebsites.net這種Hosting平台,a.azurewebsites.net跟b.azurewebsites.net往往是兩家毫無關聯的獨立公司所有,這種情境下還不視為同源也是合理的。網路上有所謂的Public Suffix List https://publicsuffix.org/list/ 記錄哪些域名有此特性,剛才查詢過,azurewebsites.net確實名列其中。(參考:https://publicsuffix.org/list/public_suffix_list.dat

# by Warren

請問一下 cookie,session也適用這一招嗎? 謝謝

# by Jeffrey

to Warren, Cookie 適用同源政策(Session也是基於Cookie),用這招也有效。

# by Warren

謝謝你的回答 我最近遇到一個問題 因為Web開發不是很熟悉 其實是接別人留下的legacy系統 有兩台SERVER 作業系統是 Win 2008R IIS 7.5 =>需要升級到Windows 2016,IIS 10 A Web Server 有設定SITEMAP 透過iframe 內嵌另一個網站的內容(網址) A Web Server Web Site 有設定redirect到另外一台 B B Web Server Web Site 有設定redirect到原本的A A web site 透過NT登入後 當USER選擇選單中選擇B網站的某一個網址 會傳遞三個參數給 B網站的進入網址(index.aspx) B網站的進入網址收到這三個參數會再解析後 做判斷並寫到其SESSION中 最近要升級到Win 2016 IIS 10 原本的程式透過VS 2015重新編譯 並以.Net 4.5.2為基底 升級後A網站的功能都正常 但是現在B網站的SESSION都會被清空 有試著把兩個網站放在同一台跑又正常 但是分開後試過底下的方法 Access-Control-Allow-Origin P3P 都會失敗(session 消失) 我後來有檢查A,B Server的網路設定(IP,DNS,DHCP,WINS) 執行完ipconfig /all A網站居然顯示 xx.aaa.com oo.aaa.com兩個domain 可是B網站居然顯示xx.aaa.com而已 我後來請負責網路的人幫忙設定跟B網站一樣 目前還在等設定完成後 再作測試 但是又看到這一篇 我又想試試看 謝謝你的回答 我星期一再試試看 感激不盡 如果正常 真不知道該如何答謝

# by cliff_chen

請問document.domain這個功能現在還有用嗎 我有2個主機 1個www.公司.com.tw 1個book.公司.com.tw 想要在www裡的頁面用iframe放入book 使用document.getElementById('iframeID').contentWindow.onmousemove= function (e) { console.log("onmove"); }.bind(this); 使用者在iframe內滑鼠移動觸發要執行的內容 在www網頁跟book網頁的body最底端都有設定<script>document.domain="公司.com.tw"</script> F12主控台還是出現這個訊息 Blocked a frame with origin "http://www.公司.com.tw" from accessing a cross-origin frame.

Post a comment