Safari第三方cookie iframe技巧不再有效?

时间:2012-03-29 17:49:05

标签: javascript facebook iframe safari

所以这是“如何让第三方cookie在Safari中工作”的第十五次复仇,但我再次提问,因为我认为比赛场地已经改变,可能是在2012年2月之后。其中一个标准技巧在Safari中获取第三方cookie如下:使用一些javascript POST到隐藏的iframe。它(曾经)欺骗Safari认为用户已经与第三方内容进行了互动,因此允许设置cookie。

认为这个漏洞已经在温和丑闻之后被关闭,据透露谷歌正在利用其广告宣传这一伎俩。至少,在使用这个技巧时,我完全无法在Safari中设置cookie。我发现了一些随机的互联网帖子,声称Apple正在努力弥补漏洞,但我没有找到任何官方消息。

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

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

20 个答案:

答案 0 :(得分:51)

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

正如我在post I made中所述:

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

index.php的顶部添加此代码,并将$page_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

答案 1 :(得分:35)

您说您愿意让用户在内容加载前点击按钮。我的解决方案是让一个按钮打开一个新的浏览器窗口。该窗口为我的域设置了一个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>

答案 2 :(得分:15)

我用.htaccess骗了Safari:

#http://www.w3.org/P3P/validator.html
<IfModule mod_headers.c>
Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CUR ADM DEV OUR BUS\""
Header set Set-Cookie "test_cookie=1"
</IfModule>

它也不再为我工作了。我的所有应用程序都在Safari中丢失了会话,并且正在重定向到Facebook。由于我急于修复这些应用程序,我目前正在寻找解决方案。我会告诉你的。

编辑(2012-04-06):显然Apple用5.1.4“修复”了它。我确信这是对Google事件的反应:“执行Cookie政策时存在问题。如果Safari中的”阻止Cookie“首选项设置为默认设置”“,则第三方网站可以设置Cookie来自第三方和广告客户“。http://support.apple.com/kb/HT5190

答案 3 :(得分:14)

在Ruby on Rails控制器中,您可以使用:

private

before_filter :safari_cookie_fix

def safari_cookie_fix
  user_agent = UserAgent.parse(request.user_agent) # Uses useragent gem!
  if user_agent.browser == 'Safari' # we apply the fix..
    return if session[:safari_cookie_fixed] # it is already fixed.. continue
    if params[:safari_cookie_fix].present? # we should be top window and able to set cookies.. so fix the issue :)
      session[:safari_cookie_fixed] = true
      redirect_to params[:return_to]
    else
      # Redirect the top frame to your server..
      render :text => "<script>alert('start redirect');top.window.location='?safari_cookie_fix=true&return_to=#{set_your_return_url}';</script>"
    end
  end
end

答案 4 :(得分:12)

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

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

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: {} }));
}

然后在父窗口(domain-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/");
    }

}

答案 5 :(得分:9)

我遇到了同样的问题,今天我找到了一个适用于我的修复程序。如果用户代理包含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已设置,并且已签名的请求会再次发布。

答案 6 :(得分:7)

我终于找到了类似Sascha提供的解决方案, 但是稍微调整一下,因为我在PHP中明确设置了cookie:

// excecute this code if user has not authorized the application yet
// $facebook object must have been created before

$accessToken = $_COOKIE['access_token']

if ( empty($accessToken) && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') ) {

    $accessToken = $facebook->getAccessToken();
    $redirectUri = 'https://URL_WHERE_APP_IS_LOCATED?access_token=' . $accessToken;

} else {

    $redirectUri = 'https://apps.facebook.com/APP_NAMESPACE/';

}

// generate link to auth dialog
$linkToOauthDialog = $facebook->getLoginUrl(
    array(
        'scope'         =>  SCOPE_PARAMS,
        'redirect_uri'  =>  $redirectUri
    )
);

echo '<script>window.top.location.href="' . $linkToOauthDialog . '";</script>';

这样做的目的是在浏览器进行Safari测试时检查cookie是否可用。 在下一步中,我们在应用程序域上,即上面提供的URL为URL_WHERE_APP_IS_LOCATED。

if (isset($_GET['accessToken'])) {

    // cookie has a lifetime of only 10 seconds, so that after
    // authorization it will disappear
    setcookie("access_token", $_GET['accessToken'], 10); 

} else {

  // depending on your application specific requirements
  // redirect, call or execute authorization code again
  // with the cookie now set, this should return FB Graph results

}

因此,在重定向到应用程序域后,会明确设置cookie,并将用户重定向到授权过程。

在我的情况下(因为我正在使用CakePHP,但它应该适用于任何其他MVC框架)我再次调用登录操作,其中FB授权再次执行,这次它由于现有成功而成功cookie中。

在授权应用程序一次后,我在Safari(5.1.6)中使用该应用程序时没有任何问题

希望这对任何人都有帮助。

答案 7 :(得分:4)

我在运行iOS的设备上遇到此问题。我创建了一个可以使用iframe嵌入普通网站的商店。不知何故,在每个页面加载时,用户都有一个新的sessionid,导致用户在进程中途陷入困境,因为会话中没有某些值。

我尝试了本页提供的一些解决方案,但弹出窗口在iPad上运行效果不佳,我需要最透明的解决方案。

我使用重定向解决了它。嵌入我网站的网站必须首先将用户重定向到我的网站,因此 top 框架包含我网站的网址,我在其中设置了Cookie并将用户重定向到网站上的相应网页嵌入我的网站,在网址中传递。

示例PHP代码

远程网站将用户重定向到

http://clientname.example.com/init.php?redir=http://www.domain.com/shop/frame

的init.php

<?php
// set a cookie for a year
setcookie('initialized','1',time() + 3600 * 24 * 365, '/', '.domain.com', false, false);
header('location: ' . $_GET['redir']);
die;

用户最终会在http://www.domain.com/shop/frame嵌入我的网站,存储会话并吃饼干。

希望这有助于某人。

答案 8 :(得分:3)

让我在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>
    }
}

答案 9 :(得分:3)

此解决方案适用于某些情况 - 如果可能:

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

答案 10 :(得分:1)

这是我使用的一些代码。我发现如果我从我的网站设置任何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();
    }

答案 11 :(得分:1)

谷歌实际上已经把这只猫从这个包里拿出来了。他们使用它一段时间来访问跟踪cookie。它几乎立即由Apple = \

修复

原始Wall Street Journal post

答案 12 :(得分:1)

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();
}

答案 13 :(得分:0)

我使用了修改(添加了signed_request param到链接)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'];
}

答案 14 :(得分:0)

我也遇到了这个问题,但终于得到了解决方案,最初直接在浏览器中加载iframe网址就像小弹出一样,然后只访问iframe中的会话值。

答案 15 :(得分:0)

Safari现在会阻止所有第三方cookie。您只能使用Storage API尝试让用户访问其第三方Cookie。

https://www.infoq.com/news/2020/04/safari-third-party-cookies-block/

答案 16 :(得分:0)

我在现有答案中并未清楚看到的某些情况(自2012年以来发生了很多变化!)

如果您可以同时控制第三方iframe和父页面(即您可以在父页面上插入JavaScript),那么可以使用几种解决方法。我建议其中最优雅的方法是使用@Frank的答案所描述的postMessage API,因为a)不需要任何重定向,b)不需要任何用户交互。

如果您无法同时控制第三方iframe和父页面,例如如果您在不受控制的网站上托管了小部件,则此处发布的大多数答案都无法在Safari as of May 2020中使用,而将在stop working in Chrome around 2022中使用。也就是说,除非用户已经访问过您的域或与iframe进行交互,否则您将无法设置Cookie。但是,有些商业服务提供了解决此问题的解决方案,例如CloudCookie.io

答案 17 :(得分:-1)

我最近在Safari上遇到了同样的问题。我发现的解决方案基于Local Storage HTML5 API。使用本地存储,您可以模拟cookie。

这是我的博客文章,其中包含详细信息:http://log.scalemotion.com/2012/10/how-to-trick-safari-and-set-3rd-party.html

答案 18 :(得分:-1)

我决定一起摆脱$_SESSION变量&amp;写了一个围绕memcache的包装来模仿会话。

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

用例:当用户登陆应用程序时,使用Session_manager存储已签名的请求,因为它在缓存中,您可以在以后的任何页面上访问它。

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

答案 19 :(得分:-9)

你可以通过添加标题来解决这个问题,因为p3p policy..i在safari上有同样的问题,所以在文件顶部添加标题后解决了我的问题。

<?php
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
?>