文章標題有點饒舌難懂,直接說我需求就清楚了。我想在員工入口網站(例如: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 Ammon

http://www.w3schools.com/jsref/obj_location.asp 用 location.hostname 會比較好

# 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 )

Post a comment


75 + 6 =