javascript - Safari浏览器第三方cookie iframe技巧不再有效?




facebook (13)

PHP中对其他人发布的内容稍微简单一些:

if (!isset($_COOKIE, $_COOKIE['PHPSESSID'])) {
    print '<script>top.window.location="https://example.com/?start_session=true";</script>';
    exit();
}

if (isset($_GET['start_session'])) {
    header("Location: https://apps.facebook.com/YOUR_APP_ID/");
    exit();
}

所以这是“如何获得第三方cookie在Safari上工作”的第十八次报复,但我再次提出要求,因为我认为赛场已经发生了变化,可能在2012年2月之后。获得第三名的标准技巧之一Safari中的第三方cookie如下所示:使用一些JavaScript将POST发送到隐藏的iframe。 它(用来)诱使Safari认为用户与第三方内容进行了交互,然后允许设置cookie。

认为这个漏洞已经在轻微的丑闻之后被封闭,因为它揭露了谷歌在广告中使用该技巧。 至少,在使用这个技巧时,我完全无法在Safari中设置Cookie。 我发掘了一些随机的网络帖子,声称苹果正在努力弥补这个漏洞,但我还没有找到官方的话。

作为一个后备,我甚至尝试重新设计主要的第三方框架,这样你就必须在内容加载之前点击一个按钮,但即使是这种直接互动水平也不足以融化Safari的冷酷心脏。

那么是否有人知道Safari是否真的关闭了这个漏洞? 如果是这样,是否有其他解决方法(除了在每个请求中手动包含会话ID)?


以下是我使用的一些代码。 我发现,如果我从我的网站设置了任何cookie,那么cookie从此开始奇妙地在iframe中工作。

http://developsocialapps.com/foundations-of-a-facebook-app-framework/

 if (isset($_GET['setdefaultcookie'])) {
        // top level page, set default cookie then redirect back to canvas page
        setcookie ('default',"1",0,"/");
        $url = substr($_SERVER['REQUEST_URI'],strrpos($_SERVER['REQUEST_URI'],"/")+1);
        $url = str_replace("setdefaultcookie","defaultcookieset",$url);
        $url = $facebookapp->getCanvasUrl($url);
        echo "<html>\n<body>\n<script>\ntop.location.href='".$url."';\n</script></body></html>";
        exit();
    } else if ((!isset($_COOKIE['default'])) && (!isset($_GET['defaultcookieset']))) {
        // no default cookie, so we need to redirect to top level and set
        $url = $_SERVER['REQUEST_URI'];
        if (strpos($url,"?") === false) $url .= "?";
        else $url .= "&";
        $url .= "setdefaultcookie=1";
        echo "<html>\n<body>\n<script>\ntop.location.href='".$url."';\n</script></body></html>";
        exit();
    }

你说你愿意让你的用户在内容加载之前点击一个按钮。 我的解决方案是有一个按钮打开一个新的浏览器窗口。 该窗口为我的域名设置了一个cookie,刷新了首域,然后关闭。

所以你的主要脚本可能看起来像:

<?php if(count($_COOKIE) > 0): ?>
<!--Main Content Stuff-->
<?php else: ?>
<a href="/safari_cookie_fix.php" target="_blank">Click here to load content</a>
<?php endif ?>

然后safari_cookie_fix.php看起来像:

<?php
setcookie("safari_test", "1");
?>
<html>
    <head>
        <title>Safari Fix</title>
        <script type="text/javascript" src="/libraries/prototype.min.js"></script>
    </head>
    <body>
    <script type="text/javascript">
    document.observe('dom:loaded', function(){
        window.opener.location.reload();
        window.close();
    })
    </script>
    This window should close automatically
    </body>
</html>

只想在这里留下一个简单的工作解决方案, 不需要用户交互

正如我在一篇文章中所说:

基本上所有你需要做的就是在top.location上加载你的页面,创建会话并将其重定向回facebook。

将此代码添加到index.php的顶部,并将$page_url设置$page_url您的应用程序的最终选项卡/应用程序URL,您将看到您的应用程序无任何问题。

<?php
    // START SAFARI SESSION FIX
    session_start();
    $page_url = "http://www.facebook.com/pages/.../...?sk=app_...";
    if (isset($_GET["start_session"]))
        die(header("Location:" . $page_url));

    if (!isset($_GET["sid"]))
        die(header("Location:?sid=" . session_id()));
    $sid = session_id();
    if (empty($sid) || $_GET["sid"] != $sid):
?>
   <script>
        top.window.location="?start_session=true";
    </script>
<?php
    endif;
    // END SAFARI SESSION FIX
?>

注意:这是为Facebook制作的,但实际上它可以在任何其他类似情况下使用。

编辑2012年12月20日 - 维护签署的请求:

上面的代码不会维护请求发布数据,并且如果您的应用程序依赖于签名请求,您可以随意尝试以下代码,否则将丢失signed_request:

注意:这仍然正在测试正确,可能不如第一个版本稳定。 使用风险自负/反馈表示赞赏。

(感谢CBroe指出我正确的方向,可以改进解决方案)

// Start Session Fix
session_start();
$page_url = "http://www.facebook.com/pages/.../...?sk=app_...";
if (isset($_GET["start_session"]))
    die(header("Location:" . $page_url));
$sid = session_id();
if (!isset($_GET["sid"]))
{
    if(isset($_POST["signed_request"]))
       $_SESSION["signed_request"] = $_POST["signed_request"];
    die(header("Location:?sid=" . $sid));
}
if (empty($sid) || $_GET["sid"] != $sid)
    die('<script>top.window.location="?start_session=true";</script>');
// End Session Fix

对于我的具体情况,我使用window.postMessage()解决了这个问题,并消除了任何用户交互。 请注意,只有在父窗口中以某种方式执行js时,这才会起作用。 要么包含来自您的域的js,要么可以直接访问源。

在iframe(domain-b)中,我检查是否存在cookie,如果未设置,则会向父级(domain-a)发送postMessage。 例如;

if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1
    && document.cookie.indexOf("safari_cookie_fix") < 0) {
    window.parent.postMessage(JSON.stringify({ event: "safariCookieFix", data: {} }));
}

然后在父窗口(域-a)监听事件。

if (typeof window.addEventListener !== "undefined") {
    window.addEventListener("message", messageReceived, false);
}

function messageReceived (e) {
    var data;

    if (e.origin !== "http://www.domain-b.com") {
        return;
    }

    try {
        data = JSON.parse(e.data);
    }
    catch (err) {
        return;
    }

    if (typeof data !== "object" || typeof data.event !== "string" || typeof data.data === "undefined") {
        return;
    }

    if (data.event === "safariCookieFix") {
        window.location.href = e.origin + "/safari/cookiefix"; // Or whatever your url is
        return;
    }
}

最后在您的服务器(http://www.domain-b.com/safari/cookiefix)上设置cookie并将其重定向回到用户来自的地方。 下面的例子是使用ASP.NET MVC

public class SafariController : Controller
{
    [HttpGet]
    public ActionResult CookieFix()
    {
        Response.Cookies.Add(new HttpCookie("safari_cookie_fix", "1"));

        return Redirect(Request.UrlReferrer != null ? Request.UrlReferrer.OriginalString : "http://www.domain-a.com/");
    }

}

我也一直在遭受这个问题,但最终得到了解决方案,最初直接在浏览器中加载iframe url像小弹出窗口,然后只访问iframe内的会话值。


我使用修改过的(在链接中添加了signed_request参数)Whiteagle的诀窍,它对safari的工作很好,但IE在这种情况下不断刷新页面。 所以我对Safari和Internet Explorer的解决方案是:

$fbapplink = 'https://apps.facebook.com/[appnamespace]/';
$isms = stripos($_SERVER['HTTP_USER_AGENT'], 'msie') !== false;

// safari fix
if(! $isms  && !isset($_SESSION['signed_request'])) {

    if (isset($_GET["start_session"])) {
        $_SESSION['signed_request'] = $_GET['signed_request'];
        die(header("Location:" . $fbapplink ));

    }
    if (!isset($_GET["sid"])) {
        die(header("Location:?sid=" . session_id() . '&signed_request='.$_REQUEST['signed_request']));
    }
    $sid = session_id();
    if (empty($sid) || $_GET["sid"] != $sid) {
    ?>
    <script>
        top.window.location="?start_session=true";
    </script>
    <?php
    exit;
    }
}

// IE fix
header('P3P: CP="CAO PSA OUR"');
header('P3P: CP="HONK"');


.. later in the code

$sr = $_REQUEST['signed_request'];
if($sr) {
        $_SESSION['signed_request'] = $sr;
} else {
        $sr = $_SESSION['signed_request'];
}

我决定一起摆脱$_SESSION变量,并写一个关于memcache的包装来模仿会话。

检查https://github.com/manpreetssethi/utils/blob/master/Session_manager.php

用例:用户登录应用程序的那一刻,使用Session_manager存储已签名的请求,并且由于它位于缓存中,因此您可以在以后的任何页面上访问它。

注意:在Safari浏览器中私人浏览时,这将不起作用,因为每当页面重新加载时,session_id都会重置。 (愚蠢的野生动物园)


我已经找到了完美的答案,所有这一切都要归功于一个名叫艾伦的人,值得所有的荣誉。 ( http://www.allannienhuis.com/archives/2013/11/03/blocked-3rd-party-session-cookies-in-iframes/

他的解决方案简单易懂。

在iframe内容服务器(域2)上,在根域级别添加一个名为startsession.php的文件,其中包含:

<?php
// startsession.php
session_start();
$_SESSION['ensure_session'] = true;
die(header('location: '.$_GET['return']));

现在在包含iframe(domain1)的顶级网站上,对包含iframe的页面的调用应如下所示:

<a href="https://domain2/startsession.php?return=http://domain1/pageWithiFrame.html">page with iFrame</a>

就是这样! 简单:)

这起作用的原因是因为您正在将浏览器导向到第三方URL并因此告诉它在从iframe中显示内容之前信任它。



我遇到了同样的问题,今天我发现了一个对我来说工作正常的修复程序。 如果用户代理包含Safari并且没有设置Cookie,我将用户重定向到OAuth对话框:

<?php if ( ! count($_COOKIE) > 0 && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari')) { ?>
<script type="text/javascript">
    window.top.location.href = 'https://www.facebook.com/dialog/oauth/?client_id=APP_ID&redirect_uri=MY_TAB_URL&scope=SCOPE';
</script>
<?php } ?>

在验证并询问权限后,OAuth对话框将重定向到位于顶部位置的我的URI。 所以设置cookie是可能的。 对于我们所有的画布和页面标签应用程序,我已经包含了以下脚本:

<script type="text/javascript">
    if (top.location.href==location.href) top.location.href = 'MY_TAB_URL';
</script>

因此,用户将被重新导向到Facebook页面标签,其中已经设置有效的cookie,并且再次发布签名的请求。


让我分享我在ASP.NET MVC 4中的修复。主要想法就像在PHP的正确答案中一样。 下面的代码添加在脚本部分的标题部分的主布局中:

@if (Request.Browser.Browser=="Safari")
{
    string pageUrl = Request.Url.GetLeftPart(UriPartial.Path);
    if (Request.Params["safarifix"] != null && Request.Params["safarifix"] == "doSafariFix")
    {
        Session["IsActiveSession"] = true;
        Response.Redirect(pageUrl);
        Response.End();
    }
        else if(Session["IsActiveSession"]==null)
    {
        <script>top.window.location = "?safarifix=doSafariFix";</script>
    }
}

这种解决方案在某些情况下适用 - 如果可能:

如果iframe内容页面使用包含iframe的页面的子域,则cookie不再被阻止。







safari