You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 19 | 让我知道你是谁HTTP的Cookie机制
在之前的[第13讲](https://time.geekbang.org/column/article/103270)、[第14讲](https://time.geekbang.org/column/article/103746)中我曾经说过HTTP是“无状态”的这既是优点也是缺点。优点是服务器没有状态差异可以很容易地组成集群而缺点就是无法支持需要记录状态的事务操作。
好在HTTP协议是可扩展的后来发明的Cookie技术给HTTP增加了“记忆能力”。
## 什么是Cookie
不知道你有没有看过克里斯托弗·诺兰导演的一部经典电影《记忆碎片》Memento里面的主角患有短期失忆症记不住最近发生的事情。
比如,电影里有个场景,某人刚跟主角说完话,大闹了一通,过了几分钟再回来,主角却是一脸茫然,完全不记得这个人是谁,刚才又做了什么,只能任人摆布。
这种情况就很像HTTP里“无状态”的Web服务器只不过服务器的“失忆症”比他还要严重连一分钟的记忆也保存不了请求处理完立刻就忘得一干二净。即使这个请求会让服务器发生500的严重错误下次来也会依旧“热情招待”。
如果Web服务器只是用来管理静态文件还好说对方是谁并不重要把文件从磁盘读出来发走就可以了。但随着HTTP应用领域的不断扩大对“记忆能力”的需求也越来越强烈。比如网上论坛、电商购物都需要“看客下菜”只有记住用户的身份才能执行发帖子、下订单等一系列会话事务。
那该怎么样让原本无“记忆能力”的服务器拥有“记忆能力”呢?
看看电影里的主角是怎么做的吧。他通过纹身、贴纸条、立拍得等手段,在外界留下了各种记录,一旦失忆,只要看到这些提示信息,就能够在头脑中快速重建起之前的记忆,从而把因失忆而耽误的事情继续做下去。
HTTP的Cookie机制也是一样的道理既然服务器记不住那就在外部想办法记住。相当于是服务器给每个客户端都贴上一张小纸条上面写了一些只有服务器才能理解的数据需要的时候客户端把这些信息发给服务器服务器看到Cookie就能够认出对方是谁了。
## Cookie的工作过程
那么Cookie这张小纸条是怎么传递的呢
这要用到两个字段:响应头字段**Set-Cookie**和请求头字段**Cookie**。
当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是“**key=value**”然后放进Set-Cookie字段里随着响应报文一同发给浏览器。
浏览器收到响应报文看到里面有Set-Cookie知道这是服务器给的身份标识于是就保存起来下次再请求的时候就自动把这个值放进Cookie字段里发给服务器。
因为第二次请求里面有了Cookie字段服务器就知道这个用户不是新人之前来过就可以拿出Cookie里的值识别出用户的身份然后提供个性化的服务。
不过因为服务器的“记忆能力”实在是太差一张小纸条经常不够用。所以服务器有时会在响应头里添加多个Set-Cookie存储多个“key=value”。但浏览器这边发送时不需要用多个Cookie字段只要在一行里用“;”隔开就行。
我画了一张图来描述这个过程,你看过就能理解了。
![](https://static001.geekbang.org/resource/image/9f/a4/9f6cca61802d65d063e24aa9ca7c38a4.png)
从这张图中我们也能够看到Cookie是由浏览器负责存储的而不是操作系统。所以它是“浏览器绑定”的只能在本浏览器内生效。
如果你换个浏览器或者换台电脑新的浏览器里没有服务器对应的Cookie就好像是脱掉了贴着纸条的衣服“健忘”的服务器也就认不出来了只能再走一遍Set-Cookie流程。
在实验环境里你可以用Chrome访问URI“/19-1”实地看一下Cookie工作过程。
首次访问时服务器会设置两个Cookie。
![](https://static001.geekbang.org/resource/image/97/87/974063541e5f9b43893db45ac4ce3687.png)
然后刷新这个页面浏览器就会在请求头里自动送出Cookie服务器就能认出你了。
![](https://static001.geekbang.org/resource/image/da/9f/da9b39d88ddd717a6e3feb6637dc3f9f.png)
如果换成Firefox等其他浏览器因为Cookie是存在Chrome里的所以服务器就又“蒙圈”了不知道你是谁就会给Firefox再贴上小纸条。
## Cookie的属性
说到这里你应该知道了Cookie就是服务器委托浏览器存储在客户端里的一些数据而这些数据通常都会记录用户的关键识别信息。所以就需要在“key=value”外再用一些手段来保护防止外泄或窃取这些手段就是Cookie的属性。
下面这个截图是实验环境“/19-2”的响应头我来对着这个实际案例讲一下都有哪些常见的Cookie属性。
![](https://static001.geekbang.org/resource/image/9d/5d/9dbb8b490714360475911ca04134df5d.png)
首先,我们应该**设置Cookie的生存周期**也就是它的有效期让它只能在一段时间内可用就像是食品的“保鲜期”一旦超过这个期限浏览器就认为是Cookie失效在存储里删除也不会发送给服务器。
Cookie的有效期可以使用Expires和Max-Age两个属性来设置。
“**Expires**”俗称“过期时间”用的是绝对时间点可以理解为“截止日期”deadline。“**Max-Age**”用的是相对时间单位是秒浏览器用收到报文的时间点再加上Max-Age就可以得到失效的绝对时间。
Expires和Max-Age可以同时出现两者的失效时间可以一致也可以不一致但浏览器会优先采用Max-Age计算失效期。
比如在这个例子里Expires标记的过期时间是“GMT 2019年6月7号8点19分”而Max-Age则只有10秒如果现在是6月6号零点那么Cookie的实际有效期就是“6月6号零点过10秒”。
其次,我们需要**设置Cookie的作用域**让浏览器仅发送给特定的服务器和URI避免被其他网站盗用。
作用域的设置比较简单,“**Domain**”和“**Path**”指定了Cookie所属的域名和路径浏览器在发送Cookie前会从URI中提取出host和path部分对比Cookie的属性。如果不满足条件就不会在请求头里发送Cookie。
使用这两个属性可以为不同的域名和路径分别设置各自的Cookie比如“/19-1”用一个Cookie“/19-2”再用另外一个Cookie两者互不干扰。不过现实中为了省事通常Path就用一个“/”或者直接省略表示域名下的任意路径都允许使用Cookie让服务器自己去挑。
最后要考虑的就是**Cookie的安全性**了,尽量不要让服务器以外的人看到。
写过前端的同学一定知道在JS脚本里可以用document.cookie来读写Cookie数据这就带来了安全隐患有可能会导致“跨站脚本”XSS攻击窃取数据。
属性“**HttpOnly**”会告诉浏览器此Cookie只能通过浏览器HTTP协议传输禁止其他方式访问浏览器的JS引擎就会禁用document.cookie等一切相关的API脚本攻击也就无从谈起了。
另一个属性“**SameSite**”可以防范“跨站请求伪造”XSRF攻击设置成“SameSite=Strict”可以严格限定Cookie不能随着跳转链接跨站发送而“SameSite=Lax”则略宽松一点允许GET/HEAD等安全方法但禁止POST跨站发送。
还有一个属性叫“**Secure**”表示这个Cookie仅能用HTTPS协议加密传输明文的HTTP协议会禁止发送。但Cookie本身不是加密的浏览器里还是以明文的形式存在。
Chrome开发者工具是查看Cookie的有力工具在“Network-Cookies”里可以看到单个页面Cookie的各种属性另一个“Application”面板里则能够方便地看到全站的所有Cookie。
![](https://static001.geekbang.org/resource/image/a8/9d/a8accc7e1836fa348c2fbd29f494069d.png)
![](https://static001.geekbang.org/resource/image/37/6e/37fbfef0490a20179c0ee274dccf5e6e.png)
## Cookie的应用
现在回到我们最开始的话题有了Cookie服务器就有了“记忆能力”能够保存“状态”那么应该如何使用Cookie呢
Cookie最基本的一个用途就是**身份识别**,保存用户的登录信息,实现会话事务。
比如你用账号和密码登录某电商登录成功后网站服务器就会发给浏览器一个Cookie内容大概是“name=yourid”这样就成功地把身份标签贴在了你身上。
之后你在网站里随便访问哪件商品的页面浏览器都会自动把身份Cookie发给服务器所以服务器总会知道你的身份一方面免去了重复登录的麻烦另一方面也能够自动记录你的浏览记录和购物下单在后台数据库或者也用Cookie实现了“状态保持”。
Cookie的另一个常见用途是**广告跟踪**。
你上网的时候肯定看过很多的广告图片这些图片背后都是广告商网站例如Google它会“偷偷地”给你贴上Cookie小纸条这样你上其他的网站别的广告就能用Cookie读出你的身份然后做行为分析再推给你广告。
这种Cookie不是由访问的主站存储的所以又叫“第三方Cookie”third-party cookie。如果广告商势力很大广告到处都是那么就比较“恐怖”了无论你走到哪里它都会通过Cookie认出你来实现广告“精准打击”。
为了防止滥用Cookie搜集用户隐私互联网组织相继提出了DNTDo Not Track和P3PPlatform for Privacy Preferences Project但实际作用不大。
## 小结
今天我们学习了HTTP里的Cookie知识。虽然现在已经出现了多种Local Web Storage技术能够比Cookie存储更多的数据但Cookie仍然是最通用、兼容性最强的客户端数据存储手段。
简单小结一下今天的内容:
1. Cookie是服务器委托浏览器存储的一些数据让服务器有了“记忆能力”
2. 响应报文使用Set-Cookie字段发送“key=value”形式的Cookie值
3. 请求报文里用Cookie字段发送多个Cookie值
4. 为了保护Cookie还要给它设置有效期、作用域等属性常用的有Max-Age、Expires、Domain、HttpOnly等
5. Cookie最基本的用途是身份识别实现有状态的会话事务。
还要提醒你一点因为Cookie并不属于HTTP标准RFC6265而不是RFC2616/7230所以语法上与其他字段不太一致使用的分隔符是“;”与Accept等字段的“,”不同,小心不要弄错了。
## 课下作业
1. 如果Cookie的Max-Age属性设置为0会有什么效果呢
2. Cookie的好处已经很清楚了你觉得它有什么缺点呢
欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。
![unpreview](https://static001.geekbang.org/resource/image/f0/97/f03db082760cfa8920b266ce44f52597.png)
![unpreview](https://static001.geekbang.org/resource/image/56/63/56d766fc04654a31536f554b8bde7b63.jpg)