javascript - Safari 3:e parts cookie iframe-trick fungerar inte längre?
Så detta är den otaliga hämnden av frågan "hur får jag tredjepartscookies att fungera i Safari" men jag frågar igen eftersom jag tror att spelplanen har förändrats, kanske efter februari 2012. En av de vanliga knepen för att få tredjepartscookies i Safari var följande: använd lite javascript för att POSTa till en dold iframe. Det (brukade) lura Safari att tro att användaren hade interagerat med tredjepartsinnehållet och tillåta sedan cookies att sättas.
Jag tror att denna lucka har stängts i kölvattnet av den milda skandalen där det avslöjades att Google använde det tricket med sina annonser. Åtminstone, medan jag använde detta trick har jag helt varit oförmögen att sätta cookies i Safari. Jag grävde fram några slumpmässiga internetinlägg som påstod att Apple arbetade på att stänga luckan men jag har inte hittat något officiellt uttalande.
Som en reservplan försökte jag till och med att omdesigna huvudramen från tredje part så att du var tvungen att klicka på en knapp innan innehållet skulle laddas, men ens den nivån av direkt interaktion räckte inte för att smälta Safaris kalla, kalla hjärta.
Så vet någon säkert om Safari faktiskt har stängt detta kryphål? Om så är fallet, finns det andra lösningar (förutom att manuellt inkludera en session-ID i varje förfrågan)?
Svar #1
Ville bara lämna en enkel fungerande lösning här som inte kräver användarinteraktion.
Som jag skrev i ett inlägg jag gjorde:
I princip är allt du behöver göra att ladda din sida på top.location, skapa sessionen och omdirigera den tillbaka till Facebook.
Lägg till denna kod högst upp i din index.php och ställ in $page_url till din applikations slutliga flik-/app-URL så kommer du att se att din applikation fungerar utan några problem.
<?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
?>
Notera: Detta gjordes för Facebook, men det skulle faktiskt fungera i alla andra liknande situationer.
Redigera 20-dec-2012 - Underhålla signerad begäran:
Koden ovan behåller inte förfrågans postdata, och du skulle förlora signed_request. Om din applikation är beroende av signed request, tveka inte att prova följande kod:
Observera: Detta testas fortfarande ordentligt och kan vara mindre stabilt än den första versionen. Använd på egen risk/Feedback uppskattas.
(Tack till CBroe för att han pekade mig i rätt riktning här vilket möjliggjorde förbättring av lösningen)
// 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
Svar #2
Du sa att du var villig att låta dina användare klicka på en knapp innan innehållet laddas. Min lösning var att ha en knapp som öppnar ett nytt webbläsarfönster. Det fönstret ställer in en cookie för min domän, uppdaterar öppnaren och stänger sedan.
Så ditt huvudskript skulle kunna se ut så här:
<?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 ?>
Då ser safari_cookie_fix.php ut så här:
<?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>
Svar #3
Jag lurade Safari med en .htaccess:
#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>
Och det slutade fungera för mig också. Alla mina appar förlorar sessionen i Safari och omdirigeras ut från Facebook. Eftersom jag har bråttom att fixa de apparna, letar jag för närvarande efter en lösning. Jag kommer att hålla dig uppdaterad.
Redigera (2012-04-06): Tydligen fixade Apple det med 5.1.4. Jag är säker på att detta är reaktionen på Google-grejen: "Ett problem fanns i tillämpningen av dess cookie-policy. Tredjepartswebbplatser kunde sätta cookies om preferensen 'Blockera cookies' i Safari var inställd på standardinställningen 'Från tredje parter och annonsörer'. http://support.apple.com/kb/HT5190
Svar #4
För min specifika situation löste jag problemet genom att använda window.postMessage() och eliminera all användarinteraktion. Observera att detta endast fungerar om du på något sätt kan köra js i föräldrafönstret. Antingen genom att låta det inkludera en js från din domän, eller om du har direkt tillgång till källkoden.
I iframe (domain-b) kollar jag om en cookie finns och om den inte är inställd kommer jag att skicka ett postMessage till föräldern (domain-a). T.ex.;
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: {} }));
}
Sedan i föräldrafönstret (domain-a) lyssna efter händelsen.
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;
}
}
Slutligen på din server (http://www.domain-b.com/safari/cookiefix) sätter du kakan och omdirigerar tillbaka till där användaren kom ifrån. Nedanstående exempel använder 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/");
}
}
Svar #5
I din Ruby on Rails-kontroller kan du använda:
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
