我如何绕过 window.opener 跨域安全

IT技术 javascript cross-domain
2021-01-20 08:13:46

我刚刚发现,如果新 URL 是跨域的,则在 IE 中window.opener通过打开的窗口中不可用window.open如何在 IE 中检测开窗器

如果窗口在我的域中启动,离开它,然后返回到我的域,就会发生这种情况。我试图在弹出窗口中进行社交注册(facebook、google 等)。完成后,它应该关闭新窗口并重定向开启程序。

我知道 Soundcloud 正在实现这一目标,但我不知道如何实现。我看到 URL 从他们的更改为 Facebook,然后关闭。

从第 3 方重定向回我的网站后,我运行以下命令:

var data = {
  type : 'complete',
  destination : '<?= $destination; ?>'
};
if ( window.opener ) {
  window.opener.postMessage( JSON.stringify( data ), '*' );
  window.close();
}
else {
  alert( "Unable to find window" );
}

它在 IE 中发出警报,即使窗口最初是我的域,然后重定向到 FB,然后重定向回我。我想可能因为我打开我的网站并立即从 PHP 重定向,这可能是一个问题。然而,即使我打开我的网站,window.location.href = 'facebookssite.com'返回时它仍然抱怨。

笔记

社交注册不适用于 .google、FB 等iframe我相信他们出于安全原因不允许他们。

6个回答

反过来做。从主(打开器)窗口跟踪子弹出窗口的状态,您可以轻松知道子窗口何时导航回您的域,以便您可以再次与它“对话”。但不要自行关闭子窗口。让 opener 窗口从子窗口获取结果然后关闭它。

例如,main.html

<!DOCTYPE html>
<head>
<title>main</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script>
window.addEventListener("message", function(ev) {
    if (ev.data.message === "deliverResult") {
        alert("result: " + ev.data.result);
        ev.source.close();
    }
});
        
function Go() {
    var child = window.open("child.html", "_blank", "height=200,width=200");
        
    var leftDomain = false;
    var interval = setInterval(function() {
        try {
            if (child.document.domain === document.domain) {
                if (leftDomain && child.document.readyState === "complete") {
                    // we're here when the child window returned to our domain
                    clearInterval(interval);
                    alert("returned: " + child.document.URL);
                    child.postMessage({ message: "requestResult" }, "*");
                }
            }
            else {
                // this code should never be reached, 
                // as the x-site security check throws
                // but just in case
                leftDomain = true;
            }
        }
        catch(e) {
            // we're here when the child window has been navigated away or closed
            if (child.closed) {
                clearInterval(interval);
                alert("closed");
                return; 
            }
            // navigated to another domain  
            leftDomain = true;
        }
    }, 500);
}
</script>
</head>
<body>
<button onclick="Go()">Go</button>
</body>

孩子.html

<!DOCTYPE html>
<head>
<title>child</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script>
window.addEventListener("message", function(ev) {
    if (ev.data.message === "requestResult") {
        // ev.source is the opener
        ev.source.postMessage({ message: "deliverResult", result: true }, "*");
    }   
});
</script>
</head>
<body>
<a href="http://www.example.com">Go to example.com</a>
Then click the browser Back button when ready.
</body>

用IE10测试。

@Sapphire_Brick,我当然不会,我希望自 2013 年以来我的风格有所改善!随意编辑答案以使其看起来更好,我会接受编辑。
2021-03-23 08:13:46
@Noseratio:如果在窗口中打开其他域的 url,您的解决方案效果很好,但是我们如何修改您的代码以处理 Facebook 内部对用户进行身份验证并只需在窗口中打开重定向的 url 的情况。在这种情况下, leftDomain 标志为 false,即使发生了一些内部重定向。
2021-04-02 08:13:46
在 IE11 中为我工作。还没有在 Edge 中测试。
2021-04-02 08:13:46
@Arthur,我对 React 的经验有限,但我不明白为什么这行不通。在 2019 年,我只会使用async/await并且可能CancellablePromise为子窗口进行状态轮询。
2021-04-03 08:13:46
@dark_shadow,我建议您将这个问题作为一个单独的问题提出,并将其链接到它。
2021-04-04 08:13:46

由于安全原因,window.opener在重定向到不同域时被删除。window.opener当您回来时,浏览器不会费心恢复在你的情况下,你可以尝试:

1) 如果可能,请在 iframe 内进行身份验证,而不是使用重定向。

2)在您的情况下,我看到您需要将数据发布回父窗口。你可以试试这个:

在您打开的窗口中,只需存储data并正常关闭即可。

var data = {
  type : 'complete',
  destination : '<?= $destination; ?>'
};

window.hasData = true;
window.data = data;
window.close();

您的父窗口可以访问您打开的窗口并可以处理其close事件:

openedWindow.beforeunload = function (){
    //here you could access this.data or openedWindow.data because you're on the same domain
    if (this.hasData){
    }
    //Reason we have this check is because the beforeunload event fires whenever the user leaves your page for any reason including close, submit, clicking a link, ...
}

3)解决方法:使用你的父页面的定时器来检查closed的性能openedWindow

setInterval(function(){
   if (openedWindow.closed){

   }
},1000);

4) 另一个使用localStorage 的解决方案,因为您在同一个域中。您的父页面可以收听事件

window.addEventListener("storage", function(event){

}, true);

你的openWindow代码:

var data = {
  type : 'complete',
  destination : '<?= $destination; ?>'
};

if (localStorage){
   localStorage.setItem(JSON.stringify(data));
}
window.close();
iframe 答案是不可能的。更新了我的问题...我上次编辑时不小心删除了该行。该间隔将不起作用,因为即使我知道它已关闭,我也不会从打开的窗口获得数据。
2021-03-17 08:13:46
@Dave Stein:你能试试本地存储吗?它应该可以工作,但恐怕它不适用于所有浏览器(有些浏览器不支持本地存储)。
2021-04-02 08:13:46
@Dave Stein:我认为可行的解决方案是尽可能在 iframe 中加载身份验证表单。使用这种方法,我们不必离开我们的域并完全避免这个问题。
2021-04-03 08:13:46
我将我的窗口引用命名为social_windowsocial_window.beforeunload = function() {从未发射过任何东西。social_window.onbeforeunload = function() {仅当我在重定向到 FB/Google 之前关闭窗口时才会触发。
2021-04-14 08:13:46
@Dave Stein:如何使用计时器?这个解决方案不好,因为我们不能有一个好的解决方案,因为这是浏览器的行为。我们所能做的就是尝试找到一种解决方法。
2021-04-14 08:13:46
  1. 从您的 iframe、网页、yoursite.com 上……在 yoursite.com 上打开一个新窗口
  2. 窗口将自己重定向到 Google、Twitter 等
  3. 完成后,OAuth 重定向会将窗口返回到 yoursite.com 上的页面
  4. 新窗口,因为与打开它的页面同源,所以可以通过 window.open 进行通信
不要使用postmessage。只需使用直接访问,您就可以这样做,因为它们具有相同的来源。window.opener.globalFuncInOpenerContext().
2021-03-18 08:13:46
新窗口将如何与其开启者通信?window.open( 'mysite.com', 'parent' )? 我认为父母在那里不会是真的。或者我是否只需要确保开启者有一个目标 ID。我已经在做 1-3
2021-03-23 08:13:46
您的示例仍然尝试调用 postMessage。只是为了澄清,window.opener.postMessagewindow.opener.anActualFunctionInOpenerContext. 这是来自我们的生产应用程序的相关代码,来自最终重定向自 Twitter/Facebook/Google 页面:gist.github.com/benvinegar/5c8e96bc7f58a8c85f4d
2021-03-25 08:13:46
假设您是正确的,我将需要使用全局,但window.opener不存在的事实阻止了我做任何事情。我现在正在 IE10 中进行测试。一旦我可以window.opener存在,我将转换为全局函数。在你的要点中,我不会通过if-statement
2021-03-25 08:13:46
我更新了我的问题。window.opener回到我的网站后,它甚至不知道是真实的。我想知道有什么不同,它对你没有问题。
2021-04-09 08:13:46

使用 localStorage 或 IndexedDB 在显示来自同一域但彼此没有引用的文档的窗口之间进行通信。

只需让一个高速计时器检查数据,保存另一条数据以确认收到,另一个窗口可以找到它并关闭。

简而言之 - 您使用 localStorage 来传递命令,甚至可以有一个库来执行此操作并在命令执行后删除它们,并发布返回值。

sessionStorage 对我来说似乎更好,但是是的。+1
2021-04-04 08:13:46
嗨,格雷戈里!你能检查一下这个问题吗?- stackoverflow.com/q/58732237/9464680
2021-04-12 08:13:46

您可以使用 use window.postMessage(),它是为此确切场景提供的。

说明:https : //developer.mozilla.org/en-US/docs/Web/API/Window/postMessage