分享 在多域名系统中同步 session - 坑与解决方案

cicholgricenchos · 2018年03月27日 · 最后由 cicholgricenchos 回复于 2018年03月27日 · 2166 次阅读

背景

我们网站是一个多域名的电商系统,有3个子站,但是共用一套购物车,添加购物车是在子域名,而下单是在主域名进行的,所以涉及到在3个域名下同步购物车的问题。这个场景可能在现实中不太常见,不过解决问题的过程还是挺有趣的,于是把几年来的一些经历做个记录。

使用ajax访问主域名下的cookie

这是最初使用的方案,所有用户信息的cookie都存在主域名,在子域名每次被打开的时候,用ajax请求来获取主域名的cookie信息,然后用这些数据来更新页面上的购物车商品数等等。用户在子域名下添加产品到购物车的请求,也是ajax发到主域名的。由于支付和账户页面都在主域名,子域名只需要很少的session信息,所以这种做法也没有什么问题。

到16年左右,为了用户隐私,各大浏览器开始禁用第三方cookie。在A站向S站发送一个ajax请求,对于A站来说S站是第三方,这个请求对S站cookie的访问就会被阻断。所以ajax这个方案不能再用了,加购物车对主域名的读写都是无效的,用户跳转到主域名之后就会发现购物车根本没商品。

使用307跳转同步session_token

既然第三方cookie被禁用了,那只要把访问变成第一方访问就好了。我们采用的方式是,每当访问子域名时,如果当前cookie中没有一个session_token,就用307跳转到主域名获取一个,写入子域名里。这样各站都能获得到一个统一的session_token,再用这个session_token到redis里取用一个session。可以看出这和oauth的流程挺像的,不过我们的系统需要在匿名的情况(允许不登录加购物车)下同步这个session_token,不像oauth有个账户id之类的凭据,偶尔会出问题。

这里使用307可以让POST请求维持原样,302则会变成GET请求。

一开始生成在子域名的client_key的作用是防止循环跳转,假如客户浏览器没有启用cookie,即使consume_auth_code的请求回来了,下一次跳转还是会发现缺少session_token,又会重新generate_auth_code,陷入循环。而consume_auth_code请求中一旦发现client_key和子站域名里的不一致,就可以终止这个流程,并提示用户启用cookie。

通过时间戳解决session不一致

然而启用了这套系统,还是有零星的购物车产品丢失的情况出现。调查发现是两个generate_auth_code请求同时到达主域名,由于主域名cookie里还没session_token,所以两个请求都生成了session_token,写入cookie然后返回。其中有一个会失效,子域名也会因此得到不同步的session_token。

当时我一直觉得这个情况是无解的,因为需要加锁,但是对两个匿名请求没办法加锁。有一天在网上聊到这个问题,有位网友才提醒我这种情况可以用乐观锁的,若当时没办法区分保留哪一个,就两个都保留,事后再统一选择一个。

于是我给cookie里的session_token键加上一个时间戳,如session_token.1511254333304564304,这样一个域名下可能出现多个session_token,但是后端总是使用时间戳最大的,然后把其他token都在redis里重定向到这个选定的token,这样就解决了不一致的问题。

使用meta跳转应对webkit对307取用cookie的封杀

苹果在safari 11里引入了一套新的机制,Intelligent Tracking Prevention,对跨站cookie的访问阻断更严格了。ITP会通过机器学习来阻断cookie访问,另外307跳转也被归到了第三方cookie的范畴,还有一些玄学的判定条件,例如用户如果没在24小时内访问S站,那么A站到S站的307跳转是访问不到S站的cookie的。

这个机制也一度让我们一筹莫展,甚至开始考虑仿照google analytics的auto linker,在url里传递session_token,然后应用浏览器指纹和ip来对某个客户端同步session。当然这是很危险的,因为用户的token要暴露到url中,可能会被利用,而且手机的浏览器指纹重合率很高,最终我们也没这么干。

最后我们死马当活马医,既然307被拦截,那200总行了吧?我们尝试了用meta refresh进行跳转,并且意外的发现是可行的,cookie访问没有被ITP拦截。于是safari 11这个问题暂时这么解决了,用js来跳转应该也是可行的。

向单域名迈进

即使这样,还是有零星的session不同步,原因五花八门,有的用户干脆禁用了我们主域名的cookie,有的单独清了主域名的cookie(主域名的token10年过期,子域名都是session级别的)。最奇葩的是firefox,部分firefox浏览器会无端延长cookie的生命,一个session级cookie甚至能活一个月之久,理论上关浏览器就应该消失的。而且还会出现单个域名下,有多套cookie轮换,例如用户需要跳转到paypal支付,一分钟后跳转回来的时候却是另一套cookie了。

于是,我们也不得不将方案逐渐改为单域名。目前,我们让所有域名都可以进行下单流程,不依赖主域名,至少这样能让用户下单当前站的商品。同时在url里带一部分session_token,可以及时发现session不一致,并且提示用户重开浏览器或清空cookie试试。

总的来说,这种多域名的方案已经不再推荐,如果有新的站点想这么干,还是及早悬崖勒马为好:-)。

共收到 2 条回复

不允许匿名加购物车的话还是有解的。

hooopo 回复

这样是可解的,就是得用户打密码登录,另外苹果的ITP把google sign in都搞的不能用了。。https://github.com/google/google-api-javascript-client/issues/342

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册