跨站台設定 Cookie 的另類解法 - window.open()
| | 0 | | ![]() |
瀏覽器禁止跨站台 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。
改為另開視窗,Cookie 就正常了。
因應 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 頁面被擋掉,網頁將提示使用者開放設定:
允許快顯後,就能一次開啟三個頁面檢查 Cookie 值:
最後,加上設定 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>
成功!
Alternative way to resolve cross-site cookie setting restriction.
Comments
Be the first to post a comment