前言
随缘记录一下,这篇文章讲解了Cookie的相关知识
为什么要有Cookie
客户端和服务器的传输使用的是http协议,http协议是无状态的,什么叫无状态,就是服务器不知道这一次请求的人,跟之前登录请求成功的人是不是同一个人,而在一些需要权限的情景下,服务器必须知道客户端的身份才能进行操作
于是,服务器想了一个办法
它按照下面的流程来认证客户端的身份
- 客户端登录成功后,服务器会给客户端一个出入证(令牌 token)
- 后续客户端的每次请求,都必须要附带这个出入证(令牌 token)
这样,服务器就可以很方便的通过令牌识别客户端的身份了,这个令牌,就是Cookie
cookie的组成
每个cookie由下面的部分组成
- key:键,比如
- value:值,它可以是任何信息
- domain:域,表达这个cookie是属于哪个网站的
- path:路径,表达这个cookie是属于该网站的哪个基路径的,就好比是同一家公司不同部门会颁发不同的出入证。比如
/news
,表示这个cookie属于/news
这个路径的。 - secure:是否使用安全传输
- expire:过期时间,表示该cookie在什么时候过期
当浏览器向服务器发送一个请求的时候,浏览器会试图带上相关的cookie
如果一个cookie同时满足以下条件,则这个cookie会被附带到请求中
- cookie没有过期
- cookie中的域和这次请求的域是匹配的
- 如果cookie中的域是
sakura-snow.com
,则可以匹配的请求域是sakura-snow.com
、www.sakura-snow.com
、blogs.sakura-snow.com
等等 - 如果cookie中的域是
www.sakura-snow.com
,则只能匹配www.sakura-snow.com
这样的请求域 - cookie是不在乎端口的,只要域匹配即可
- 如果cookie中的域是
- cookie中的path和这次请求的path是匹配的
- 比如cookie中的path是
/news
,则可以匹配的请求路径可以是/news
、/news/detail
、/news/a/b/c
等等,但不能匹配/blogs
- 如果cookie的path是
/
,就能够匹配所有的路径
- 比如cookie中的path是
- 验证cookie的安全传输
- 如果cookie的secure属性是true,则请求协议必须是
https
,否则不会发送该cookie - 如果cookie的secure属性是false,则请求协议可以是
http
,也可以是https
- 如果cookie的secure属性是true,则请求协议必须是
如果一个cookie满足了上述的所有条件,则浏览器会把它自动加入到这次请求中
具体加入的方式是,浏览器会将符合条件的cookie,自动放置到请求头中,例如,当我在浏览器中访问百度的时候,它在请求头中附带了下面的cookie:
它的格式是键=值; 键=值; 键=值; ...
,每一个键值对就是一个符合条件的cookie。
如何设置cookie
由于cookie是保存在浏览器端的,同时,很多证件又是服务器颁发的
所以,cookie的设置有两种模式:
- 服务器响应:这种模式是非常普遍的,当服务器决定给客户端颁发一个证件时,它会在响应的消息中包含cookie,浏览器会自动的把cookie保存起来
- 客户端自行设置:这种模式少见一些,不过也有可能会发生,比如用户关闭了某个广告,并选择了「以后不要再弹出」,此时就可以把这种小信息直接通过浏览器的JS代码保存到cookie中。后续请求服务器时,服务器会看到客户端不想要再次弹出广告的cookie,于是就不会再发送广告过来了。
服务器端设置cookie
服务器可以通过设置响应头,来告诉浏览器应该如何设置cookie
响应头按照下面的格式设置:
1 | set-cookie: cookie1 |
通过这种模式,就可以在一次响应中设置多个cookie了,具体设置多少个cookie,设置什么cookie,根据你的需要自行处理
其中,每个cookie的格式如下:
1 | 键=值; path=?; domain=?; expire=?; max-age=?; secure; httponly |
每个cookie除了键值对是必须要设置的,其他的属性都是可选的,并且顺序不限
当这样的响应头到达客户端后,浏览器会自动的将cookie保存起来,如果依旧存在相同的Cookie(其他key、path、domain相同),则会自动的覆盖之前的设置。
下面,依次说明每个属性值:
- path:设置cookie的路径。如果不设置,浏览器会将其自动设置为当前请求的路径。比如,浏览器请求的地址是
/login
,服务器响应了一个set-cookie: a=1
,浏览器会将该cookie的path设置为请求的路径/login
- domain:设置cookie的域。如果不设置,浏览器会自动将其设置为当前的请求域,比如,浏览器请求的地址是
http://www.sakura-snow.com
,服务器响应了一个set-cookie: a=1
,浏览器会将该cookie的domain设置为请求的域www.sakura-snow.com
- 这里值得注意的是,如果服务器响应了一个无效的域,浏览器是不认的
- 什么是无效的域?就是响应的域连根域都不一样。比如,浏览器请求的域是
sakura-snow.com
,服务器响应的cookie是set-cookie: a=1; domain=baidu.com
,这样的域浏览器是不认的。
- expire:设置cookie的过期时间。这里必须是一个有效的GMT时间,即格林威治标准时间字符串,比如
Fri, 17 Apr 2020 09:35:59 GMT
,表示格林威治时间的2020-04-17 09:35:59
,即北京时间的2020-04-17 17:35:59
。当客户端的时间达到这个时间点后,会自动销毁该cookie。 - max-age:设置cookie的相对有效期。expire和max-age通常仅设置一个即可。比如设置
max-age
为1000
,浏览器在添加cookie时,会自动设置它的expire
为当前时间加上1000秒,作为过期时间。- 如果不设置expire,又没有设置max-age,则表示会话结束后过期。
- 对于大部分浏览器而言,关闭所有浏览器窗口意味着会话结束。
- secure:设置cookie是否是安全连接。如果设置了该值,则表示该cookie后续只能随着
https
请求发送。如果不设置,则表示该cookie会随着所有请求发送。 - httponly:设置cookie是否仅能用于传输。如果设置了该值,表示该cookie仅能用于传输,而不允许在客户端通过JS获取,这对防止跨站脚本攻击(XSS)会很有用。
下面来一个例子,客户端通过post
请求服务器http://sakura-snow.com/login
,并在消息体中给予了账号和密码,服务器验证登录成功后,在响应头中加入了以下内容:
1 | set-cookie: token=123456; path=/; max-age=3600; httponly |
当该响应到达浏览器后,浏览器会创建下面的cookie:
1 | key: token |
于是,随着浏览器后续对服务器的请求,只要满足要求,这个cookie就会被附带到请求头中传给服务器:
1 | cookie: token=123456; 其他cookie... |
现在,还剩下最后一个问题,就是如何删除浏览器的一个cookie呢?
如果要删除浏览器的cookie,只需要让服务器响应一个同样的域、同样的路径、同样的key,只是时间过期的cookie即可
所以,删除cookie其实就是修改cookie
下面的响应会让浏览器删除token
1 | cookie: token=; domain=yuanjin.tech; path=/; max-age=-1 |
浏览器按照要求修改了cookie后,会发现cookie已经过期,于是自然就会删除了。
无论是修改还是删除,都要注意cookie的域和路径,因为完全可能存在域或路径不同,但key相同的cookie
因此无法仅通过key确定是哪一个cookie
客户端设置cookie
既然cookie是存放在浏览器端的,所以浏览器向JS公开了接口,让其可以设置cookie
1 | document.cookie = "键=值; path=?; domain=?; expire=?; max-age=?; secure"; |
可以看出,在客户端设置cookie,和服务器设置cookie的格式一样,只是有下面的不同
- 没有httponly。因为httponly本来就是为了限制在客户端访问的,既然你是在客户端配置,自然失去了限制的意义。
- path的默认值。在服务器端设置cookie时,如果没有写path,使用的是请求的path。而在客户端设置cookie时,也许根本没有请求发生。因此,path在客户端设置时的默认值是当前网页的path
- domain的默认值。和path同理,客户端设置时的默认值是当前网页的domain
- 其他:一样
- 删除cookie:和服务器也一样,修改cookie的过期时间即可
后记
咳,其实了解下就行,我现在除了一个项目里,后端同学用了一个框架自动设了cookie导致了一个很奇怪的bug,其他时候都没用过