我们知道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
字段,只要在一行里用 ;
隔开就行
如果你换个浏览器或者换台电脑,新的浏览器里没有服务器对应的Cookie
,就好像是脱掉了贴着纸条的衣服,健忘的浏览器就认不出来了,只能再走一遍Set-cookie
的流程
下面来看个例子,客户端首先发送一个请求,服务端响应报文中设置Set-Cookie
响应头
然后刷新这个页面,浏览器就会在请求头里自动送出 Cookie,服务器就能认出你了。
如果换成 Firefox 等其他浏览器,因为 Cookie 是存在 Chrome 里的,所以服务器就又“蒙圈”了,不知道你是谁,就会给 Firefox 再贴上小纸条。
Cookie的属性
说到这里,你应该就知道了,Cookie就是服务器委托浏览器存储在客户端里的一些数据,而这写数据通常会记录用户的一些关键识别信息,所以就需要key=value
外再用一些手段来保护,防止外泄或窃取,这些手段就是 Cookie
属性
首先我们应该设置 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的作用域,让浏览器仅发送给特定的服务器和URL,避免其他网站盗用
作用域的设置比较简单 Domain
和Path
制定了Cookie所属的域名和路径,浏览器在发送Cookie前会从URI中提取出host和path部分,对比cookie的属性,如果不满足就不会在请求头里发送cookie
使用这两个属性可以为不同的域名和路径分别设置各自的 Cookie,比如“/19-1”用一个 Cookie,“/19-2”再用另外一个 Cookie,两者互不干扰。不过现实中为了省事,通常 Path 就用一个“/”或者直接省略,表示域名下的任意路径都允许使用 Cookie,让服务器自己去挑。
最后要考虑的就是Cookie的安全性了,尽量不要让服务器以外的人看到
写过前端的同学一定知道。在JS脚本里用document.cookie
来读写cookie数据,这就带来了安全隐患,有可能会导致跨站脚本攻击窃取数据
属性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。
Cookie的应用
现在回到我们最开始的话题,有了Cookie,服务器就有了记忆能力,能够保存 “状态”,那么该如何使用Cookie呢
Cookie最基本的一个用途是身份识别,保存用户的登录信息,实现会话事物
比如你用账号和密码登录某电商,登录成功后网站服务会给浏览器发送一个Cookie,内容大概是 name=youid
,这样就成功地把身份标签贴在了你身上
之后你在网站里随便访问哪件商品的页面,浏览器都会自动把身份 Cookie 发给服务器,所以服务器总会知道你的身份,一方面免去了重复登录的麻烦,另一方面也能够自动记录你的浏览记录和购物下单(在后台数据库或者也用 Cookie),实现了“状态保持”。
你上网的时候肯定看过很多的广告图片,这些图片背后都是广告商网站(例如 Google),它会“偷偷地”给你贴上 Cookie 小纸条,这样你上其他的网站,别的广告就能用 Cookie 读出你的身份,然后做行为分析,再推给你广告。
这种 Cookie 不是由访问的主站存储的,所以又叫“第三方 Cookie”(third-party cookie)。如果广告商势力很大,广告到处都是,那么就比较“恐怖”了,无论你走到哪里它都会通过 Cookie 认出你来,实现广告“精准打击”。
为了防止滥用 Cookie 搜集用户隐私,互联网组织相继提出了 DNT(Do Not Track)和 P3P(Platform for Privacy Preferences Project),但实际作用不大。
总结
- Cookie 是服务器委托浏览器存储的一些数据,让服务器有了“记忆能力”;
- 响应报文使用 Set-Cookie 字段发送“key=value”形式的 Cookie 值;
- 请求报文里用 Cookie 字段发送多个 Cookie 值;
- 为了保护 Cookie,还要给它设置有效期、作用域等属性,常用的有 Max-Age、Expires、Domain、HttpOnly 等;
- Cookie 最基本的用途是身份识别,实现有状态的会话事务。
还要提醒你一点,因为 Cookie 并不属于 HTTP 标准(RFC6265,而不是 RFC2616/7230),所以语法上与其他字段不太一致,使用的分隔符是“;”,与 Accept 等字段的“,”不同,小心不要弄错了。
Cookie这个词来源于计算机编程语言里的术语 “Magic Cookie”,意思是不透明的数据,并不是小甜饼的含义
早期Cookie直接就是磁盘上的一些小文本文件,现在基本上都是以数据库记录的形式存放(通常使用的是Sqlite),浏览器对Cookie的数量和大小也有限制,不允许无限存储,一般总大小不超过4K
如果不指定Expires或Max-Age属性,那么Cookie仅在浏览器运行时有效,一旦浏览器关闭就会失效,这被称为会话Cookie(session cookie)或内存Cookie(in-memory cookie)在Chrome里过期实现会显示为 Session 或 N/A