早期互联网只是用于简单的页面浏览,并没有交互,服务器也无法知道不同的请求是否来自同一个浏览器,不知道某用户上一次做了什么。每次请求都是相互完全独立的,这也是 HTTP 协议无状态特征的表现。这种缺陷显然无法满足交互式 Web 发展的需求,Cookie 作为一种解决这一问题的方案,被当时最强大的网景浏览器公司提出。

一、Cookie叙述

Cookie 可以理解成浏览器的身份证。不同站点会根据实际情况,发放一个唯一的身份证或不发放。当再次访问相同站点时,按照约定要带上这个身份证来享受部分特权。如果身份证丢了,那就要重新登记办理。

Cookie 信息是由客户端浏览器自身维护的。不同的浏览器有不同的客户端本地存储方式,Chrome 和 Firefox 使用 SQLite 存储,IE 使用的是文本格式。Cookie 里面重要的 key&value 都是被浏览器加密存放的,只有通过给定的 API 方式才能获取存入的原始数据。默认情况下,Cookie 信息会随着浏览器进程的结束而从内存中销毁,如果由于某些需求,服务器端设置了 Cookie 的存活时间,那么这个 Cookie 就会以某种形式被存储在磁盘上,在有效存活期内不会被清理,可以被重复使用并更新其生命周期。

1.1 观察Cookie在HTTP数据包中的交互

  这里以 http://www.website.com/bbs/ 站点为例,说明 Cookie 在 HTTP 协议包里是如何传输的。

第一次请求 bbs 首页 /bbs/ 时,在请求数据包的 header 部分是没有 Cookie 信息的。这时,因为某些功能需要,站点会要求在浏览器本地存储 Cookies,如下面第一次交互请求登陆页面,本地浏览器会存储三个变量: phpbb3_lhc4d_u , phpbb3_lhc4d_k , phpbb3_lhc4d_sid 。在这里要重点关注 phpbb3_lhc4d_sid 这个变量,它存储的是服务器端 SessionID 的值(也可以称呼它为会话标识符)。

GET /bbs/ HTTP/1.1
Host: www.website.com.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:04:44 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Set-Cookie: phpbb3_lhc4d_u=1; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_k=; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:04:44 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 9110
Connection: close
Content-Type: text/html; charset=UTF-8

刷新页面,再次请求同一个页面,可以发现浏览器会自动把之前存储在本地的对应站点 Cookies 全部提交过去。这里提一下,每一个 Cookie 都是有大小限制的,大约在 4k 左右。

GET /bbs/ HTTP/1.1
Host: www.website.com.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:05:16 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:05:16 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 8435
Connection: close
Content-Type: text/html; charset=UTF-8

输入正确的账号信息进入 bbs 内部后,会发现服务器更新了存储在本地 Cookie 中的 phpbb3_lhc4d_sid 信息,它是你能留在 bbs 内部板块的通行证,只要访问的时候带上这个 Cookies 就可以畅通无阻。

POST /bbs/ucp/php?mode=login HTTP/1.1
Host: www.website.com.cn
Content-Length: 94
Cache-Control: max-age=0
Origin: http://www.website.com.cn
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/bbs/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16
Connection: close

username=***********&password=**************************&login=%E7%99%BB%E5%BD%95&redirect=.%2Findex.php%3F
HTTP/1.1 302 Found
Date: Mon, 21 Oct 2019 03:05:48 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Set-Cookie: phpbb3_lhc4d_u=77; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_k=; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_sid=bdabd760e3a87aa6b0dfb517c8c7d90a; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Location: http://www.website.com.cn/bbs/index.php?&sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Cache-Control: max-age=86400
Expires: Tue, 22 Oct 2019 03:05:48 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 0
Connection: close
Content-Type: text/html
GET /bbs/index.php HTTP/1.1
Host: www.website.com.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/bbs/index.php?&sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:06:16 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:06:16 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 37722
Connection: close
Content-Type: text/html; charset=UTF-8

分析上述 HTTP 数据包的交互:

  • Cookie 是使用 HTTP 的头部来传递和交换信息的;
  • Set-Cookie 是服务器端给浏览器下发指令的关键字,可以带上一些控制属性;
  • Cookie 是浏览器端发送给服务器端消息字段,它只能是 name=value 格式,无法带上其他属性。

二、Cookie有哪些属性

RFC6265 中规定,通常 Set-Cookie 响应头部包含头部名称为「Set-Cookie:尾随的 Cookie」。尾随的 Cookie 除了 name, value 这两个必备属性外,还有几个其他的控制属性可选。相关描述摘录如下:

属性 描述
必须属性 变量名称:值
可选属性 描述摘录
expires 过期属性 如果这个属性的值不能被转换为日期,客户端会忽略该属性。 当同一个 Cookie 两次请求的 expires 值不相同时,新的可能会替换旧的。
max-age 最大缓存时间属性 相对过期时间,以秒为单位。 如果该属性的值不是数字,客户端将不做处理。(max-age 优先级大于 expires)
domain 域属性 如果没有设置 Cookie 的 domain 值,该属性的默认值就是创建 Cookie 的网页所在的服务器域名。
path 路径属性 Cookie 的适用范围为路径设置所限制。 如果服务器忽略路径属性,用户客户端会将请求 uri 路径元素的名录当做缺省值。
secure 安全属性 它指定了在网络上如何传输 Cookie 值。 默认情况下,Cookie 是不安全的,也就是说,它们是通过一个普通的、不安全的 HTTP 链接传输的。 但是如果将 Cookie 标记为安全的,那么它将只在浏览器和服务器通过 HTTPS 或其他安全协议链接时才被传输。 这个属性只能保证 Cookie 是保密的。
HttpOnly 属性 HttpOnly 属性限制 Cookie 的 HTTP 请求的适用范围,只能通过 HTTP 访问,不能通过 document.cookie 获取设定为 HttpOnly 的键值,防止 XSS 读取 Cookie。 Cookie 可以同时拥有 HttpOnly 以及安全属性。

三、Cookie的应用场景

3.1 HTTP会话状态保持

单纯的Cookie会话管理——

pic

相对安全的Cookie会话机制——

pic

在功能实现上,两者都没有问题,但在实际使用中会发现 Cookie 本身大小数量的限制和信息本地保存会带来一定的安全问题(如可能会暴露给坏人,或被坏人重放)。一般使用中不推荐把敏感信息存放于 Cookie 中,而是采用存储会话标识符(SessionID)到 Cookie 里的方式,服务器端可以通过获取这个会话标识符(SessionID)关联当前状态信息。当然了,使用会话标识符并非没有风险,相对于前一种方式已是个好的改善。

打个比方,假设浏览器对应自然人,Cookie 对应身份证,而 Web 服务器端对应的是户籍管理处,那么,Web 服务器(户籍管理处)需要负责给浏览器(自然人)发放 Cookie(身份证)。后期的会话鉴别就是通过保存在客户端浏览器的 Cookie(身份证姓名和号码)里的「会话标识符」实现的,所有的敏感信息都存储在服务器端(户籍管理处),而非交给第三方的浏览器来保管。

3.2 基于Cookie的SSO单点登录

同域和非同域下SSO单点登录

在同域下实现 Cookies 单点登录相对而言简单一些。将 Cookie 的 domain 属性配置好父域名,如 .website.com.cn ,那么这个 Cookie 就可以被类似 a.website.com.cnb.website.com.cnsso.website.com.cnwebsite.com.cn 共享使用。有一点需要特别注意,浏览器请求子域会带上父域的 Cookie,反之则不会。按照约定,我们把需要共享的 Cookie 信息写入到 website.com.cn 这个域名下即可。

pic

以上讨论的是同域情况下的 SSO 单点登录过程。那么跨域情况下又是怎么个原理呢?继续来看流程图,相比上述同域情况会多了跳来跳去的动作。

pic

3.3 跟踪分析用户行为

「大数据」颠覆了某些旧的束缚,有能力去分析全部数据,可得出准确的大方向,而不再仅凭随机采样数据做分析。在互联网行业,「大数据」表现在采集和分析网络用户行为数据,并推送需求上。说到网络用户行为数据的采集,就不能不谈谈 Cookie 在此所扮演的另类角色。

客户端访问个性化设置

网站的各项配置参数可以存储在浏览器本地 Cookies 中,当客户端浏览器再次访问此站点时,直接通过读取 Cookies 信息即可完成相关的个性化配置。当然,局限性也很明显,Cookies 到期失效或换一台电脑,效果就没有了。这是针对临时用户而言的。有会员注册功能的站点,一般将配置保存在 Web 服务器本地数据库,这是持久存在的。

搜索引擎&定向广告推送

在搜索引擎或购物站点搜索产品信息后,再浏览其他网页时,经常在其广告区域显示曾经搜索过的产品或相关信息。哪天小伙伴借你电脑临时一用,打开游览器,你的隐私可能就暴露了,很是讨厌,而这是 Cookie 的功劳。

pic

网络广告公司信息推广

还有一种可能,你打开的某站点很有可能还内嵌了一种叫做网页臭虫的图片。该图片透明且只有一个像素大小,我们称呼它为 Pixel Code,其作用是向所有访问过此页面的客户端写入定制的 Cookie (通过不同站点收集用户爱好习惯)。当你访问与网络广告推广有合作的购物站点时,这些 Cookie 信息就会被读取并被有针对性地推荐广告。整个流程和上面的搜索引擎&定向广告类似,在此不再作图赘述了。

四、Cookie的安全问题探讨

作为一个和 HTTP 协议打交道多年的安全从业者,还是忍不住想讨论下这个问题——

我们知道 Cookie 是保存在用户本地的,由各自的浏览器自行维护。拿 Chrome 浏览器来说,用户可以直接在浏览器内输入 chrome://settings/siteData 来访问它的 Cookies 存储信息,这种方式是通过浏览器 API 获取,可以完整地看到原始 value 信息。如果用户想通过其他手段查看,还可以通过 chrome://version/ 发现数据存储的位置,仔细查看后,不难发现,%LOCALAPPDATA%\Google\Chrome\UserData\Default\Cookies 就是它的 Cookies 存储文件。这是一个 SQLite 轻型数据库,通过工具打开查询后可发现,domain 是 baidu 的都可以被搜索出来,如下图。这里可以看到,name=value 的 value 部分被浏览器进行了一次加密存储处理。

pic

聊完 Chrome 的本地存储,再说说 Cookie 本身的特点。Cookie 通常可能记录了用户账号 ID、密码、会话标识符 SessionID,这些信息可能是加密,也可能是未加密的。加密存储只能说明其在安全上做了些功课,但被截获(抓包、XSS)后,是否加密并无太大意义。坏人无需看懂那串密语,只需要把这些信息照葫芦画瓢丢给服务器,就可以实现越权操作了。而这些安全性问题,可以阅读相关词条(见Ref1)了解学习。   (易树国 | 天存信息)   Ref  

  1. cookie 储存在用户本地终端上的数据’ - 百科词条
  2. HTTP State Management Mechanism’ - RFC6265