来份 cookie?

最开始的时候由于不了解浏览器的存储方式,加上前端写的多,导致一直使用前端操作 cookie。现在对 cookie 作一个简单探究。

为什么需要 cookie

大约在今年二月份,Chrome 新版进一步对第三方 cookie 做了限制。然而与业界同行(Firefox,Safari)比起来,还是有所差距,但是距离第三方 cookie 被完全禁用不远了。

第三方 cookie 涉及到跨域等等问题,所以本篇只简单提到,默认 cookie 为第一方 cookie。

cookie 最初的任务是记住用户状态。而正是这个原因,cookie 会被限制,却几乎不可能取消。

了解 http 协议就知道 http 是无状态的。一个 http 完成后,同页面发出的第二个 http 无法与第一个 http 没有任何关系。考虑购物网站,用户可能同时打开很多购物页面。如果 http 无状态,那么每个页面都要重新登录,这是很难忍受的。此外还有购物车。无协议的 http 几乎没有可能完成购物车功能。为了强行让 http 有状态,必须要有一个东西在不同页面间通信。这就是 cookie。

如果用过 python 爬虫就可能知道:模拟登录的最好方法是直接获取登录 cookie。此后每次请求只要带上这个 cookie 就能获取登录后的页面。

cookie 是如何做到的

首先思考下服务器如何识别用户。最简单办法是通过对话(session)生成一个唯一识别的 token,服务器每次校验这个 token,就能识别用户了。

http 无状态的情况不会发生改变,但是 http 是一个报文,那么我们自然可以在报文内添加我们的信息。假设报文内部有一个 token,服务器通过识别这个唯一确定的 token 就能准确识别出用户出来。所以最简单方法,每次发送请求都手动设置一个 header 用来存储 token。

分析下这样的问题:

  1. 麻烦,每次都需要手动设置,容易出错。
  2. 不安全,这样的话需要前端处理。换句话说 cookie 是暴露在浏览器的,如果 js 操作不当会产生问题。

事实上,cookie 刚刚诞生(网景公司)的时候,js 远没有现在这么发达,所以没有第二条问题。可以简单理解为为了解决第一个问题,cookie 诞生了。

cookie 的特点是浏览器会在每个要发出的 http 报文的 cookie 字段附带上合理(稍后讨论)的全部 cookie。而服务器操作 cookie 的方式则是在响应报文中加入 set-cookie 字段。

也就是说 cookie 是服务器设置的,也是服务器利用的。

这是个人理解:cookie 诞生的目的从来不是给前端使用,而是服务器端的概念。所以后面提到的 session 概念其实在前端是没有的。对前端来说,只有一个个 http 报文,然后读取服务器指令(有无登陆,信息)执行而已。websocket 也是类似,没有本质区别。

cookie 与 localstorage

我们首先先不讨论 cookie 与 session 的关系,而是考虑前端关心的存储问题。

我们应该在哪里存储我们前端的数据。

之前有看到群里讨论,说是用户偏好数据应该存储在服务器还是前端。这才引发了下面的思考。下面以 twitter 为例子进行分析。

远古时代

cookie 历史之前提到过,已经很老了(网景时代),但是 cookie 每次都需要发送到服务器。显然 cookie 不能太大,不然一个个报文发送过去服务器吃不消。所以虽然 cookie 进行了几次扩容,但是本身空间不大(4kb)。当然一般完全够用了。

js 发展后,自然而然也用 cookie 存储数据。但是之前观点提到,cookie 并不是给前端使用的。换句话说用 cookie 存储数据没问题,但是不合理。

之前没有理解的时候,为了记住用户评论的用户名,我错误的把用户名存储到了 cookie 内,现在想来是不合理的。

在建立连接的双方(发送接受)之间,应该有三重数据:发送方自用数据,接受方自用数据,双方共享数据。cookie 应该归类为双方共享数据,而服务器保存自己数据是很容易的,所以遇到的第一个问题是,发送方(浏览器)没有自己独属的数据存储。这才导致了通过共享数据存储的问题。

localstorage

为了能在浏览器保存数据,引入了 localstorage 的概念。

顾名思义,其是为了能在本地存储数据,而不用作通信需求。关于 localstorage 的 api, MDN文档 很齐全,可以直接查看,使用也非常简单。

localstorage 的最大特点是存储空间比 cookie 大了很多(一般为 5m,比 cookie 至少大了千倍)。

用起来大概是这样的:

localStorage.setItem("name", "value")
let value = localStorage.getItem("name", "value")
localStorage.removeItem("name")
// 清除所有
localStorage.clear()

看起来只能存储字符串,但是别忘了我们有 json。依靠 json 我们可以实现存储复杂对象。此外别忘了 localstorage 存储空间往往是足够的。

localstorage 用途

之前只提到了使用以及特点,接下来尝试讨论下用处。也就是什么时候需要本地存储。

  • 记住用户填写字段
  • 设置用户偏好

暂时只想到这么多,这也是用的最多的几个。

记住填写字段比如用户之前输入搜索结果,或者填写到一般的表格。当用户不小心刷新了网站,通过 localstorage 迅速还原自然有很棒的用户体验。

关于偏好,其实是有时需要讨论的。

对于没有登陆需求的网站,自然最方便的是直接存储在 localstorage 中。

但是假设有多个用户,每个偏好不一样(比如是采用亮色还是暗色主题),怎么办,是选择附带到 cookie 里面,还是直接存储到本地?

我的想法是存储在本地,这样的话可以迅速还原设置。而且一个很重要的理由是:偏好设置往往是页面体验,这应该是前端负责的,不应该后端关心。而且这种偏好变动往往频繁,每次都要求后端进行修改,并浪费空间存储,是不明智的。

twitter 就是采用这种结构,你可以在两个浏览器同时登陆你的帐号。当一个浏览器采用暗色主题时,另一个浏览器不论怎么刷新,还是采用默认的亮色主题。

当然考虑到业务相关设计(比如复杂换肤系统,用户花了大力气调整的,不保存下来会产生很大体验问题),当然还算存储到服务器了。

总之,localstorage 是最灵活,最方便的,很多前端进行调优都可以使用这个。

cookie 与 session

之前提到了,cookie 是服务器管理操作的,虽然前端可以获得 cookie,但是这是相当危险的。如果有人有 xss 或者 csrf 攻击,那么你的 cookie 可能就到了人家数据库。这就完蛋了。

而之前提到的 localstorage,我们也往往不会存放重要数据(可能只是用户偏好设置),所以即使暴露出去也没事。

为了防止上述攻击发生,服务器可以指定 cookie 为 http-only,也就是说这个 cookie 只用于传输,cookie 的设置与删除修改都是服务器说了算,js 脚本无法读写它们。

这样人家随意写的 js 脚本就不至于随意控制你的账户了。然而,你还是可以通过各种手段看到设置的 cookie,并做邪恶的事情(模拟登陆!爪巴虫!)。

现在开始讨论 session。前面提到,前端相对 cookie 概念比较淡,而 session 更只是一个概念,原理上是只存在于服务器上的。

session 场景

session 最广泛的应用场景可能是购物车了。购物车的情况下,用户可能打开许多页面,在不同页面加入购物车,最后在某一个页面下定主意下单。显然,这些页面都需要共享。那么 session 就是做这个的。

此外需要注意的一点是 session 数据是存储在服务器的,浏览器拿到的只是一个 id。可以理解为你拿着收据,通过收据可以去指定地方兑换奖励(数据)一样。显然有没有效是兑奖的地方(服务器)说了算,你的收据本身没有任何意义。

简单讨论 session

事实上 session 比较复杂,我后端写的也有限,所以只能简单讨论。

但是个人感觉各种网上教程都有各种问题,写的不清不楚,直接翻文档过于详细,太硬核了。

为了快速理解,从这些方面理解:

  • 何时创立
  • 何时有效
  • 何时失效与避免

何时创建

创建在任何你从头打开一个链接的时候。

举例来说,任何时候你从地址栏输入 url,然后进入,都会创建一个新 session。而这也就回答了失效问题。

同时由于 session 是同域名共享的,所以显然 a.com 不会共享 b.com,而哪怕是 www.a.comblog.b.com 也是不会共享的。也就是说必须是同级域名。这同时回答了有效问题。

何时有效

前面提到需要同域名。在同一域名的条件下,在某个已创建 session 的域名下,打开新链接,将共享这个 session。比如用户在 mall.a.com/item1 加入购物车,然后在相关链接中点开了 mall.a.com/item2 的链接,那么这是同一个 session,在第二个 url 中可以完整看到购物车。

但是这样将会变成非同一个 session:

用户点开了 mall.b.com/item1,并加入了购物车。这是听说朋友推荐了一个链接: mall.b.com/item2。 于是用户在地址栏拷贝这个链接并打开,结果是创建了两个 session,存在看不到之前购物车的风险。

换句话说,必须是在已建立 session 的页面内打开链接。

显然这个限制就很多了,接下来看如何解决。

何时失效

前面提到了在不同域名的情况下会失效,同时用户直接打开链接也会失效。此外,显然会失效的场景是重新打开浏览器。

但是用户行为永远无法预测,你永远无法预测用户会以哪种方式打开,所以显然必须要避免。

其实避免方法很多,最简单的是通过 cookie 存储 sessionid。通过 cookie 的持久化存储发送 id,从而让服务器识别 session。此外还可以 localstorage 一齐上。淘宝就是采用了类似的架构,不论你是在哪个网站打开,甚至不同域名(天猫与淘宝)都可以共享信息。

这里就不仔细提了,下面介绍重点。

cookie

了解了 cookie 与 session 使用场景,接下来查看下 cookie 性质。

这篇写的非常棒,可以康康。

可以通过以下途径了解:

  • 何时有效
  • 为何这么设计
  • 例外
  • 第三方 cookie

首先不考虑路径问题。

何时有效

前面提到,任何 http 报文都会自动将 cookie 上报,所以问题在于 cookie 的设置域与作用域。

先说结论:域名等级越高,权限越大:

a.b.c.com 的权限比 b.c.com 与 c.com 的作用域都大。这里权限大的意思是指设置与修改读取。

前面提到,使用 js 操作 cookie 是不安全的,所以之后提到的都是通过服务器的 set-cookie 来进行设置,以及服务器进行读取。

结论发出了,网上已经有大量测试,感兴趣可以查看。

假设用户在 b.a.com 下,那么他发送的 cookie 包含 b.a.com 下的,同时包含 a.com 下的。但是假设用户在 a.com 下,发送的 cookie 只有 a.com 下的,读取不到 b.a.com 下的。同时既然能读取,设置权限是一样的,这就是为什么说等级越高权限越大,高等级域名能读写低等级域名。

此外还有 path 问题,这个就非常好理解了,通常情况下,我们设置在根目录下。

由于我们不使用 js 进行操作,所以就不给出示范了。但是理解了原理,自然很简单。

为何这么设计

思考下设计原理。这类似继承,即父子类继承的关系。子类除了继承父类全部属性(cookie)外,还能有自己私有属性。

再思考下合理性。一般而言域名等级不会很高,往往三级就够了,显然其实遇到这种问题几率很小。如果遇到多级域名,那么基本上是一个 app 的子功能,那么获取父类的 cookie 也是完全没问题的,这样有助于实现功能。

再考虑下不合理性:githubpage。

我们知道给出一个子域名,人人都可以注册一个 github.io 的网页,可能是这个样子的: hello.github.io。 那么根据权限,我们知道 github.io 有风险,因为人们可以读取 github.io 上的数据。最简单的一点,某人丧心病狂将 cookie 的 4k 数据在 github.io 上塞满了,那么任何人的 github.io 页面也不得不带上全部 4k 数据。像 github.io 这样大浏览量的域名,额外的 4k 数据是疯狂的(微型 ddos 哈哈哈)。此外,占满的空间自然会影响 cookie 正常运作。

显然这样一个域名有风险,如果你打开 http://github.io, 你会发现跳转到了 github.com 的页面上。显然这样的风险页面,github 也没打算采用(我猜测的原因)。

但是这不能杜绝上述情况,必须要强制手段。

这时,例外就来了。

例外

例外叫做 PUBLIC SUFFIX LIST。在这个列表中的设置,cookie 将强制不生效。打开这个列表,你会发现 github.io 在里面。同样在里面的还有 com.cn。

简单来说,只要在这个列表中,你的 cookie 设置是不会生效的,浏览器本身就会拦截它。同时我们也认清了 com.cn 的本质:与我们通常注册的 com 域名没有任何区别,虽然可能域名等级提高了,但是操作与同在 public suffix list 里的 github.io 是一样的。

第三方 cookie

第三方 cookie 在最开头就提过,由于趋势最后应该是被禁止(目前 safari 与 firefox 做到了),所以不过多讨论。

简单来说,同域名设置 cookie 是合理的,那么跨域名呢?比如 a.com 设置给 b.com。这个 cookie 就是第三方 cookie。

应用的地方在哪里呢?比如 天猫淘宝 分属两个域名,但是都是用同一个账户登陆的。理想环境是登陆其中一个另一个就免登陆,前面简单讨论 session 靠不住,所以必须结合 cookie 使用。这个 cookie 不得不跨域。被禁止后,跨域免登录可能比较难实现了。

此外还有第三方分析工具,需要依赖第三方设置 cookie 然后发送给不同域名。这些都会受到影响。最简单的广告商无法通过你浏览的网页给你推销商品,因为他难以读取你的网页。

google 迟迟不完全禁止也是这个原因。失去第三方 cookie 会导致收集用户偏好难度增大,从而冲击它的广告行业。

当然前面也提到了,只是难度加大,意味着是有办法绕开的。各种实现更复杂,等用到的时候再说吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容