Cookie 知识二则

Cookie 知识二则

SSO?你需要知道的

SSO,即单点登录,其作用是用户登录一遍之后可在几个不同应用之间实现登录态。如当你在 A 网站登录了之后,在 B 网站也是登录状态了。实现 SSO 的方法有很多,一个要点是需要有一个标示某个用户已经登录的凭证,且该凭证能够在多个系统之间共享。使用 Cookie 保存相关的信息是一个广泛被使用的方法。那么,如何做到登录网站 A 之后,B 网站也变成已登录状态呢?

假设 A 系统域名为example-a.com,其登录页地址为 login.example-a.com,B 系统域名为example-b.com,当登录了 A 系统之后,B 系统也变为登录态,可以这样做:

  1. login.example-a.com 登录
  2. 登录成功后,向 B 系统提供的一个 API 发起请求,这个 API 是设置 B 系统的 Cookie 的,调用时带上对应 token
  3. B 系统的 API 响应时具有 Set-Cookie 头部,包含了对应的 token,将会将 Cookie 保存到 B 系统域名下

这种 Cookie,被称为第三方 Cookie,这是在 A 系统域名下设置了 B 系统域名的 Cookie。不过,第三方 Cookie 是可以被限制的,如:

  1. 在 Safari 下,在偏好设置中勾选阻止跨站跟踪可以阻止第三方 Cookie 的设置(需要注意的是 Safari 在默认情况下就开启了阻止跨站跟踪)。
  2. 在 Chrome 下,在 Cookie 设置中勾选阻止第三方 Cookie 可以阻止第三方 Cookie 的设置。

如果浏览器开启了这种配置,会导致上述的 SSO 方法失效。

此外,响应返回时最好带上 P3P 头部:

P3P: CP=CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR

另一种方案是:

  1. 访问 B 系统
  2. 若未登录,通过几次重定向从 B 系统重定向到 A 系统某URL
  3. A 系统重定向回 B 系统某URL,并带上对应参数
  4. B 系统 Set-Cookie
  5. B 系统变为登录态

这样的方式就没有上面所说的第三方 Cookie 的困扰了,这里有一篇文章描述了阿里、京东 SSO 的每一个步骤。

Cookie 还有优先级?

谷歌有一个提案 draft-west-cookie-priority-00 - A Retention Priority Attribute for HTTP Cookies,提议给 Cookie 增加一个 Priority 属性,不过,目前也只有 Chrome 实现该提案。

我们知道,浏览器存储 Cookie 是有限的,不同的浏览器有不同的限制,如一些浏览器限制的是一个域名下 Cookie 的总数量以及单个 Cookie 的大小,一些浏览器限制的则是一个域名下所有 Cookie 的总大小等,并且,整个浏览器能保存的 Cookie 量也是有限制的,对于具体数据,你可以在这个网站详细查看。

当增加新 Cookie 将会导致 Cookie 超出存储上限的时候,浏览器会剔除旧的 Cookie 以容纳新 Cookie。

在不考虑这个提案实现的情况下,根据 RFC 6265 - HTTP State Management Mechanism,剔除 Cookie 的顺序如下:

  1. 从过期的 Cookie 中
  2. 从 Cookie 超出预定义数量的域名中
  3. 从所有 Cookie 中

其中还指出如果两个 Cookie 优先级相同的话,应剔除具有最早访问时间的 Cookie(在 Browser cookie restrictions 中提到 Firefox 触发 Cookie 剔除的时候采取随机的策略,但目前在最新版 Firefox 上没有这种现象)。

而根据上面 Priority 的提案,设置 Cookie 的时候可以指定一个 Priority 属性,Priority 可取的值为:

  1. Low
  2. Medium(默认为该值)
  3. High

也就是说,我们可以像下面这样在设定 Cookie 时指定 Priority:

Set-Cookie: key=value; Priority=Medium;

提案要求实现该特性的客户端需要为每一种优先级的 Cookie 设定一个至少保留值。在移除 Cookie 的时候,执行如下策略(每一条都遵循 LRA 的策略剔除 Cookie):

  1. 从过期的 Cookie 中
  2. 从 Cookie 数超出预定义数量的域名中删除优先级为 Low 的 Cookie,并保留其最小保留值
  3. 从 Cookie 数超出预定义数量的域名中删除优先级为 Low 和 Medium 的 Cookie,同时需要保留优先级为 Low 以及优先级为 Medium 的 Cookie 至少保留量之和的 Low 与 Medium 的 Cookie
  4. 从 Cookie 超出预定义数量的域名中
  5. 从所有 Cookie 中

举个例子,假设我们实现的一个客户端同一个域名最多可存储的 Cookie 上限为 12,至少保留 2 个优先级为 Low 的 Cookie,4 个优先级为 Medium 的 Cookie,2 个优先级为 High 的 Cookie,当触发 Cookie 逐出时,最终保留 8 个 Cookie,其结果如图所示:

实现 Priority 的 Cookie 触发移除时的演示

可以看到,从左到右第 1、3、4、5、7 的 Cookie 被删除了。

PS:对于某个域名 Cookie 超出限制时,Chrome 每次触发逐出后会保留该域名下 150 个 Cookie。

引入 Priority 的意义是,某些 Cookie 比另外的 Cookie 更加重要,如保存登录凭证的 Cookie 比保存用户某个偏好设置的 Cookie 更加重要,对于无关紧要的 Cookie,我们可以设置低优先级(Low),而对于重要的 Cookie,则可以设置高优先级(High)。

谷歌似乎对 Cookie 的优先级问题十分上心,在 2017 年又有一个提案draft-ietf-httpbis-rfc6265bis-02 - Cookies: HTTP State Management Mechanism,其 Cookie 移除策略如下:

  1. 从过期的 Cookie 中
  2. 从 Cookie 数超出预定义数量的域名中删除未设置 secure-only 标志的 Cookie
  3. 从 Cookie 超出预定义数量的域名中
  4. 从所有 Cookie 中

目前 Chrome 对 Cookie 的实现是上面两种的混合,其优先级从低到高为:

  1. 优先级为 Low 的非 secure Cookie
  2. 优先级为 Low 的 secure Cookie
  3. 优先级为 Medium 的非 secure Cookie
  4. 优先级为 Medium 的 secure Cookie
  5. 优先级为 High 的非 secure Cookie
  6. 优先级为 High 的 secure Cookie

Chrome 会按照从低到高的顺序最多进行 6 轮移除过程,当移除非 secure 的 Cookie 后已经到达对应优先级的至少保留值时,不会再去剔除对应优先级的 secure Cookie。

同样举上面那样限制的一个客户端实现的例子,其结果如下:

当前 Chrome 实现策略的一个示例

可以看到,在每个对应的 Priority 级别的 Cookie 里,非 secure 的 Cookie 会先于 secure Cookie 被逐出。

虽然这里详细描述了 Cookie 逐出的策略,但是超出浏览器限制也是比较少的现象,如果出现也需要考虑自己是否存在滥用 Cookie 的现象(如将调查问卷的答题情况全部保存在 Cookie 中这种行为)。

发布于 2018-11-23

文章被以下专栏收录

    只看代码的话,上 https://github.com/ElemeFe 。这一群人,关心的不是「如何写前端」而是「如何很好地运行一个 ( web ) APP」;这一群人,会在监控屏上加上弹幕,会让实习生自主招聘,会设计、编写、监控整个 APP 的生命周期;这一群人,玩的时候... 更卖力,就像从来没来过那般卖力,卖力地热爱生活。所以这些创作大多基于 ❤️