4

跨站台設定 Cookie 的另類解法 - window.open()

 1 year ago
source link: https://blog.darkthread.net/blog/set-cross-site-cookies/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

跨站台設定 Cookie 的另類解法 - window.open()-黑暗執行緒

瀏覽器禁止跨站台 Cookie 傳送是老問題,尤以 IFrame 內嵌跨站台網頁最明顯,在 IE 時代還有「信任的網站」這招大絕,但隨著 IE 走入歷史,加上瀏覽器對於跨站台 Cookie 限制日趨嚴格,這類老寫法用起來愈來愈吃力。

先來簡單展示,假設有個設定及顯示 Cookie 的 cookie.aspx 如下:

<%@Page language="C#"%>
<script runat="server">
    string cookieName = "MyCookie";
    void Page_Load(object sender, EventArgs e)
    {
        if (Request["m"] == "set")
        {
            Response.Cookies[cookieName].Value = Request["v"];
            Response.Write("<div>Cookie is set.</div>");
            Response.Write("<script>if (window.opener) window.close();<" + "/script>");
        }
        else
        {
            Response.ContentType = "text/plain";
            Response.Write("Cookie=" + Request.Cookies[cookieName]?.Value);
        }
    }
</script>

為方便測試,我利用 Windows\System32\drivers\etc\hosts 將 my-svr-1、my-svr-2、my-svr-3 都指向 127.0.0.1,用同一台 IIS 扮演四台不同主機模擬跨站台情境。我寫了一個測試網頁 iframe-test.html 用 IFrame 內嵌 my-svr-1 到 my-svr-3 的 cookie.aspx:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Display Cross Site Cookie</title>
    <style>
        iframe {
            display: block; width: 150px; height: 50px;
        }
    </style>
</head>
<body>
    <div class="frames">
        <iframe src="http://my-svr-1/aspnet/xscookies/cookie.aspx"></iframe>
        <iframe src="http://my-svr-2/aspnet/xscookies/cookie.aspx"></iframe>
        <iframe src="http://my-svr-3/aspnet/xscookies/cookie.aspx"></iframe>
    </div>
    <script>
        document.querySelectorAll("iframe").forEach(function (iframe) {
            let title = document.createElement('div');
            title.innerText = iframe.src;
            iframe.parentNode.insertBefore(title, iframe);
        });
    </script>
</body>

</html>

如下圖所示,即使 my-svr-1 Cookie 存在,當被內嵌在 frame-test.html 時,瀏覽器不會傳送 Cookie。

Fig1_637995896546495712.png

改為另開視窗,Cookie 就正常了。

Fig2_637995896548347674.png

因應 IFrame 的跨站台 Cookie 限制,網站理應改用替代做法,無腦解法是改成另開視窗或另開頁籤,精緻一點可考慮用 postMessage 與 IFrame 跨網站網頁互動,但這需要兩邊網頁同步配合修改,工程較大多半得從長計議。

這陣子我有個需求是想從 A 站台(假設為 localhost)重設 B、C、D 站台(假設為 my-svr-1、my-svr-2、my-svr-3)的 Cookie,由於是測試環境才有的特殊狀況,不想花太多功夫處理,便想到一個簡單粗暴的解法,順便當 JavaScript 練習,裡面有些細節挺有趣,值得筆記備忘。

原理是用 window.open 逐一開啟 B、C、D 站台的 cookie.aspx?m=set 設定 Cookie 並自動關閉。這裡會遇到兩個問題:1) 按鈕動作只能合法觸發一次 window.open() 其餘兩次會被快顯封鎖器擋掉,若偵測到快顯封鎖要提示使用者開放;2) cookie.aspx 設定完會 window.close(),程式需偵測三個新開視窗都結束再執行下一步。

第一步,先寫一小段程式用 window.open() 及上回介紹的定位技巧檢視 B、C、D 站台的 Cookie 內容:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Setting Cross Site Cookie</title>
    <style>
        .frames { margin-top: 12px;}
        .frames > iframe { float: left; width: 100px; height: 50px; }
        #popupBlockerWarn { display: none; color: red; }
        .winSlots > span {
            margin: 12px; display: inline-block;
            width: 200px; height: 120px; background-color: #eee;
        }
    </style>
</head>
<body>
    <div id="popupBlockerWarn">
        請調整封鎖快顯視窗設定,允許彈出視窗
    </div>
    <button onclick="checkCookies()">Check</button>
    <span id="spnMsg"></span>
    <div class="winSlots">
    </div>
    <script>
        function showPopupBlockerWarn() {
            document.getElementById("popupBlockerWarn").style.display = "block";
        }
        var sites = [
            "http://my-svr-1/aspnet/xscookies/cookie.aspx",
            "http://my-svr-2/aspnet/xscookies/cookie.aspx",
            "http://my-svr-3/aspnet/xscookies/cookie.aspx"
        ];
        sites.forEach((u,i)=> {
            let slot = document.createElement('span');
            slot.setAttribute('id', 'slot' + i);
            document.querySelector('.winSlots').appendChild(slot);
        });
        var wins = [];
        function openWinOnElem(elemId, url) {
                let baseElem = document.getElementById(elemId);
                let baseRect = baseElem.getBoundingClientRect();
                let x = baseRect.left + window.screenX ;
                let winHeaderHeight = window.outerHeight - window.innerHeight;
                let y = baseRect.top +  window.screenY + winHeaderHeight;
                return win = window.open(url, '_blank', 
                    `popup=yes,width=${baseRect.width},height=${baseRect.height - winHeaderHeight},left=${x}px,top=${y}px`);
        }
        function checkCookies() {
            if (wins.length) return;
            sites.forEach((u, i) => {
                let win = openWinOnElem('slot' + i, u);
                if (!win) showPopupBlockerWarn();
                else wins.push(win);
            });
            let countDown = 10;
            let h = setInterval(function () {
                let msg = document.getElementById('spnMsg');
                msg.innerText = `視窗關閉倒數: ${countDown--}`;
                if (countDown <= 0) {
                    wins.filter(w => !w.closed).forEach(w => w.close());
                    wins = [];
                    msg.innerText = '';
                    clearInterval(h);
                }
            }, 1000);
        } 
    </script>
</body>

</html>

未允許快顯前,按鈕只會顯示站台 B 頁面,C、D 頁面被擋掉,網頁將提示使用者開放設定:

Fig3_637995896550169067.png

允許快顯後,就能一次開啟三個頁面檢查 Cookie 值:

Fig4_637995896551921591.png

最後,加上設定 Cookie 程式,開啟 cookie.aspx?m=set,使用 window.closed 偵測 cookie.aspx 是否執行完畢自動關閉,都完成後觸發檢查結果:

    <input type="text" placeholder="Cookie Value" id="txtValue" />
    <button onclick="setCookies()">Set</button>    
    
    <script>
        //...略
        var winOptions = 'popup=yes,status=no,scrollbars=no,resizable=no,width=100,height=50';
        function setCookies() {
            var v = document.getElementById("txtValue").value;
            sites.forEach(u => {
                var win = window.open(u + "?m=set&v=" + encodeURIComponent(v), 
                    '_blank', winOptions);
                if (!win) showPopupBlockerWarn();
                else wins.push(win);
            });
            var h = setInterval(function () {
                if (wins.length == 0) {
                    clearInterval(h);
                    checkCookies(); 
                }
                else wins = wins.filter(w => !w.closed);
            }, 100);
        }           
    </script>

Fig5_637995896556261489.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK