当浏览器默认禁用第三方cookie

前一阵子,我们发现高版本的Safari中默认会阻止第三方cookie,如下图所示。

Safari默认阻止第三方cookie

问题

什么是第三方cookie呢?在访问一个网站A时,网站A算作第一方,如果网站A中引用了另一个网站X(网站X的域名与网站A的域名不同)的资源,这时这个网站X就被认为是第三方。需要注意的是,这儿区分不同网站的标准是域名是否相同,而不是这两个网站是否由同一个公司运营。比如,taobao.com和tmall.com被认为是两个网站,尽管它们都属于阿里集团。

在网站建设中,使用第三方资源非常常见,大多数据情况下,这并不会带来问题。不过有时候,我们可能希望能读写这个第三方域下的cookie,这时问题就来了。

比如我们有一个网站a.com,其中埋有一段JavaScript脚本,每当用户打开a.com中的页面时,这段脚本就会发送一个GET请求到x.com。这样,只需要分析x.com的日志,就可以了解a.com的访问情况。

如果只是要统计a.com的PV,那么只需要将x.com的日志中所有记录加起来就行了,但是,如果要统计UV呢?

这时就需要在x.com这个域下写一个cookie用于标识当前用户,比如叫USER_ID。当用户访问a.com的页面,也即发起到x.com的请求时,x.com的服务器检查x.com域下是否有USER_ID这个cookie,如果有则什么也不做,如果没有,则生成一个新的USER_ID并写入cookie。有了这个cookie之后,分析x.com的日志就可以同时得到a.com的PV与UV。整个打点过程如下图所示。

打点 使用第三方cookie

但问题是,x.com在这儿属于第三方域,在高版本的Safari浏览器中,向第三方域写cookie受到了阻止。带来的结果就是,用户每次访问a.com时,发向x.com的请求的cookie都为空,于是x.com的服务器每次都认为这是一个新访问者,每次都生成一个新的USER_ID写回去,但当同一个用户再访问下一个a.com的页面时,发向x.com的请求的cookie仍然为空。最后,分析x.com的日志时就会发现,访问PV没有变化,但UV却暴涨,几乎和PV持平。

或许有人会问,打点服务器为什么要使用第三方域x.com呢?如果使用与站点相同的域a.com不就没有问题了吗?的确,如果打点服务器与站点同域那就没有问题了,不过很多时候我们并不能做到这一点,比如我们可能需要向很多个域名完全不同的站点提供同一套打点服务。

这个问题目前并不算严重,因为还只有高版本的Safari有这样的限制。但是,Safari增加这个限制是为了保持用户隐私(因为有大量的广告站点滥用第三方cookie),很有可能在不久的将来其他浏览器也会跟进,因此我们不得不尽早寻找解决之道。

P3P方案在这儿也是走不通的,KISSY的开发者承玉曾经提出过一个解决方案,简单来说,就是使用POST来代替GET,这样就能继续在高版本的Safari中写入第三方cookie。但遗憾的是这个方案在Safari 5.1.4+的版本中失效了,估计Safari已经修复了漏洞。另外,Google曾经因为使用类似的方式继续在高版本Safari下读写第三方cookie而被告上法庭,在去年8月时被罚款2250万美元,也就是说,如果继续使用各种hack的方式绕过浏览器限制读写第三方cookie,有可能面临法律风险。而且随着浏览器不断升级,各种原来可用的hack方式也都陆续失效了。

因此,必须要寻找其他解决方案。

第二方方案

经过测试,我们发现目前Safari只会在第三方域下完全没有cookie时阻止第三方cookie,而第三方域下只要有过任意一个cookie,即可继续使用以前的方式顺利读写。但是,怎么才能在第三方域下写入第一个cookie呢?

我们测试了很多方案,包括iframe嵌套等,最后发现至少在Safari 6中,如果第三方域下完全没有cookie,那么就没有办法向其写入cookie,唯一的办法是将它变成第二方,也即让这个域在顶层窗口打开。也就是说,如果第三方域下没有cookie,要向它写入第一个cookie,要么将页面先跳到这个域,写入cookie,再跳回来,要么弹出一个新窗口,写入cookie,再关闭弹窗。

显然,这两种方案对用户体验来说都不好。

localStorage方案

我们注意到,高版本Safari只阻止了第三方cookie,并没有阻止第三方localStorage,于是,我们便有了一个更为激进的方案:放弃第三方cookie,使用localStorage来代替。

这个方案的本质是这样的:在a.com中嵌入一个w.com域的iframe,这个iframe读取localStorage(是w.com域下),取到各种原来需要保存在第三方cookie中的值,然后发送一个GET或POST请求到x.com,原来那些记录在cookie中随着HTTP请求头发送的信息则改为通过url参数(GET方式)或Form表单(POST方式)的形式发送。如果要发送的内容不多,那么可以使用GET方式发送,只需返回一个jsonp即可,然后iframe再将jsonp中的数据写入localStorage。如果需要发送的内容很多,有可能使URL超长,那么就需要使用POST方式发送,这时,需要在iframe中再创建一个iframe作为POST的target,然后新iframe再将数据用postMessage等方式传回原iframe,原iframe再写回localStorage。

整个过程(使用POST)如下图所示:

打点 使用localStorage

这个方案的问题是比较复杂,整个流程长了很多,需要用到一些HTML5特性,比如localStorage、postMessage等,不过好在不支持第三方cookie的浏览器基本上都是对HTML5支持良好的高版本浏览器。

就目前来看,比较保险的做法是新老方案并行,在老浏览器上继续使用第三方cookie,在高版本Safari等默认阻止第三方cookie的浏览器上使用新方案。虽然不完美,但确实是可行的。期待不久的将来能有一种更完美的方案。

 

2013-03-26更新:Firefox开始跟进Safari默认阻止第三方cookie的策略

11 Replies to “当浏览器默认禁用第三方cookie”

    1. 其实要解决的问题是浏览器能记住这个用户,因此似乎必须得在本地写点什么。如果用参数传递,用户完全关闭浏览器后再次访问时(可能访问的是另一个不同域名的子站或相关站点),我们怎么知道他以前来过没有呢?

  1. 这篇文章很好,但是我有些疑问:浏览器和应用的本地存储都在各自不同的sandbox中,Mobile Safari 本地存储的UUID,在App中是无法读取的。如果我没理解错的话,那么这个方法应该是行不通的。当然也可能是我没明白你的方法,还请指正。谢谢。

    1. 不太明白你的问题,这儿说的方案都是在浏览器中的,你说的是在移动平台上,浏览器和别的APP共享数据吗?

  2. 请问下localstorage方式中,为什么需要加载w.com的iframe,并将用户信息存储在w.com下的localstorage中,而不是直接存在a.com下的localstorage中呢?

    1. 完全可以存储在a.com下,不过我们有很多域名会共用一套打点系统,除了a.com外还有b.com、c.com …,为了做得更通用一点,同时为了避免维护a.com的同学无意中修改或删除我们的值,所以用了一个独立的w.com来存储信息。

  3. Pingback引用通告: zenxds的部落格

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s