【问题标题】:How do I get around window.opener cross-domain security如何绕过 window.opener 跨域安全
【发布时间】:2013-09-08 15:54:06
【问题描述】:

我刚刚发现window.opener 在通过window.open 打开的窗口中不可用,如果新的URL 是跨域的,在IE 中。 How do I detect window opener in 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' 在返回时仍然抱怨。

注意

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

【问题讨论】:

    标签: javascript cross-domain


    【解决方案1】:

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

    例如,ma​​in.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>
    

    child.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 测试。

    【讨论】:

    • @Noseratio:如果在窗口中打开其他域的 url,您的解决方案效果很好,但是我们如何修改您的代码以处理 Facebook 在内部对用户进行身份验证并在窗口中打开重定向的 url 的情况。在这种情况下,leftDomain 标志为 false,即使发生了一些内部重定向。
    • @dark_shadow,我建议您将此作为一个单独的问题提出并将其链接到它。
    • 在 IE11 中为我工作。尚未在 Edge 中测试。
    • @Arthur,我对 React 的经验有限,但我不明白为什么这不起作用。在 2019 年,我只会使用 async/await 或者类似 CancellablePromise 的东西来对子窗口进行状态轮询。
    • @Sapphire_Brick,我当然不会,我希望我的风格自 2013 年以来有所改善!请随时 edit the answer 让它看起来更好,我会接受编辑。
    【解决方案2】:

    出于安全原因,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) 解决方法:在父页面中使用计时器检查openedWindowclosed 属性

    setInterval(function(){
       if (openedWindow.closed){
    
       }
    },1000);
    

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

    window.addEventListener("storage", function(event){
    
    }, true);
    

    您打开的窗口代码:

    var data = {
      type : 'complete',
      destination : '<?= $destination; ?>'
    };
    
    if (localStorage){
       localStorage.setItem(JSON.stringify(data));
    }
    window.close();
    

    【讨论】:

    • 我将窗口引用命名为social_windowsocial_window.beforeunload = function() { 从未解雇过任何东西。 social_window.onbeforeunload = function() { 仅在我在重定向到 FB/Google 之前关闭窗口时触发。
    • @Dave Stein:使用计时器怎么样?这个解决方案不好,因为我们不能有一个好的解决方案,因为这是浏览器的行为。我们所能做的就是尝试找到解决方法。
    • @Dave Stein:我认为可行的解决方案是尽可能在 iframe 中加载身份验证表单。使用这种方法,我们不必离开我们的域并完全避免这个问题。
    • iframe 答案是不可能的。更新了我的问题...我上次编辑时不小心删除了该行。间隔不起作用,因为即使我知道它已关闭,我也不会从打开的窗口中获得数据。
    • @Dave Stein:你能试试本地存储吗?它应该可以工作,但恐怕它不适用于所有浏览器(有些浏览器不支持本地存储)。
    【解决方案3】:
    1. 从您的 iframe、网页、yoursite.com ... 在 yoursite.com 上打开一个新窗口
    2. 窗口将自身重定向到 Google、Twitter 等
    3. 完成后,OAuth 重定向会将窗口返回到 yoursite.com 上的页面
    4. 新窗口,因为它与打开它的页面具有相同的来源,可以通过 window.open 进行通信

    【讨论】:

    • 新窗口将如何与其开启者通信? window.open( 'mysite.com', 'parent' )?我认为父母在那里不会是真实的。还是我只需要确保开瓶器有一个目标 ID。我已经在做 1-3
    • 不要使用 postmessage。只需使用直接访问,您可以这样做,因为它们具有相同的来源。 window.opener.globalFuncInOpenerContext().
    • 我更新了我的问题。返回我的网站后,甚至不知道window.opener 是真实的。我想知道它对你没有问题有什么不同。
    • 您的示例仍然尝试调用 postMessage。澄清一下,window.opener.postMessagewindow.opener.anActualFunctionInOpenerContext 不同。以下是我们生产中应用程序的相关代码,来自最终重定向的 Twitter/Facebook/Google 页面:gist.github.com/benvinegar/5c8e96bc7f58a8c85f4d
    • 当然,我需要使用全局变量是正确的,但 window.opener 不存在的事实阻止了我做任何事情。我现在正在IE10中进行测试。一旦我可以让window.opener 存在,我将切换到一个全局函数。在你的要点我不会通过if-statement
    【解决方案4】:

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

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

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

    【讨论】:

    【解决方案5】:

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

    解释:https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

    【讨论】:

      【解决方案6】:

      在我的公司,我们有不同的域,并且存在内部网站点必须获取我们的公共网站的情况(最终摆脱重复数据的维护)。 在 Ben Vinegar 的启发下,我得出了这个解决方案的简单解决方案,避免了:

      调用域网页(在我的情况下与外部网页同名)

      本地'getInfo.php'

      <?php 
            $idSp = (isset($_GET['idSp'])?$_GET['idSp']:null);
            echo file_get_contents('http://192.168.1.10/folder/getInfo.php?idSp='.$idSp);
       ?>
      

      外部'getInfo.php'返回

       <?php  
          echo '<script>window.opener.manageDisplay('.$getRes.','.$isOK.');</script>';
          if($auto_close){ echo "<script>window.close();</script>"; }
        ?>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        • 2014-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-29
        • 2010-12-16
        相关资源
        最近更新 更多