这是我参与更文挑战的第1天,活动详情查看:更文挑战
cookie 简介
HTTP 是无状态的协议,协议本身不保留之前的请求或相应报文的信息,这是为了减少服务器的资源消耗,更快地处理大量事务,确保协议的可伸缩性,使协议能够适用在更多场景。
但是在很多场景下服务端都需要明确请求是来自于哪个用户,期望客户端能够将用户的登录状态等信息告知服务端,于是 HTTP/1.1 引入了 Cookie,实现用户识别跟状态信息的管理。
不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过4KB。超过限制以后,Cookie 将被忽略,不会被设置。
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
为 Cookie 服务的 HTTP 首部字段:
-
HTTP 响应,通过 Set-Cookie 字段进行Cookie配置
HTTP/1.1 200 OK ... Set-Cookie: name=LvLin;domain=github.com;path=/blog Set-Cookie: id=test123;
-
浏览器向服务器发送 HTTP 请求时,会自动带上满足条件的 Cookie(具体条件见下文)
GET /sample_page.html HTTP/1.1 Host: www.github.com Cookie: name=value; name2=value2; name3=value3
可以通过 document.cookie 返回当前网页的 Cookie,也可以进行新 Cookie 的创建,但是 document.cookie 无法创建和访问具有 HttpOnly 标记的 Cookie。
document.cookie // "name=LvLin;id=test123"
document.cookie = "a=1" // 等号两边不能有空格
document.cookie // "a=1; name=LvLin; id=test123"
如果想改变一个已存在的 Cookie(通过 Set-Cookie 或 document.cookie ),需要key
、domain
、path
和secure
都匹配,否则会出现两个同名的 Cookie,在发起请求的时候如果满足条件则都会被带上,后端无法区分。
cookie 的属性介绍
name、value
Cookie 的内容信息,以 name=value 的形式进行存储,区分大小写。
domain
domain 属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。
可以设置 domain 为当前服务器域名或者父域名。如果未设置,则该 Cookie 值只能在发向该域名的请求时被携带。如果设置了 domain,则发向该域以及子域的请求都会带上该 Cookie。
由于历史原因,存在前缀点的 domain,有前缀点表示允许子域请求携带该cookie,无前缀点则不允许。目前规范规定忽略前缀点。
path
path 指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。如果配置的时候设置了path,那么在该path下的子路径都允许携带该 cookie。
Expires、Max-Age
这两个字段用于定义 Cookie 的生命周期。Expires 是一个绝对时间,如果没有设置这个属性或者设置为 null,则表示这是一个Session Cookie。需要注意的是,浏览器是根据本地时间与 Expires 对比判断是否过期,而本地时间是可能变动的,所以无法保证 Cookie 一定会在服务器指定的时间过期。
Max-Age 是一个相对时间,是在 Cookie 失效之前需要经过的秒数。秒数为 0 或 -1 将会使 Cookie 直接过期。
HTTP/1.1 200 OK
...
Set-Cookie: id=test123; Expires=Tue, 01 Jun 2021 11:45:54 GMT;
如果同时指定了 Expires 和 Max-Age ,那么 Max-Age 的值将优先生效。如果都没有指定,那这个 Cookie 就是 Session Cookie,一旦用户关闭浏览器,这个 Cookie 就会被删除。有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,Session Cookie 也会被保留下来,就好像浏览器从来没有关闭一样,这会导致 Cookie 的生命周期无限期延长。
SameSite
SameSite Cookie 允许服务器要求某个 cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。总共有三个值:Strict,Lax,None。原默认为 None,先规定默认为 Lax,为 None 时必须具有 Secure 属性。
三种规则对应的 Cookie 发送情况如下所示:
请求类型 | 实例 | Strict | Lax | None |
---|---|---|---|---|
链接 | <a href="..."></a> | 不发生 | 发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..." /> | 不发送 | 发送 Cookie | 发送 Cookie |
get 表单 | <form method="GET" action="..."> | 不发送 | 发送 Cookie | 发送 Cookie |
post 表单 | <form method="post" action="..."> | 不发送 | 不发送 | 发送 Cookie |
iframe | <iframe src="..."></iframe> | 不发送 | 不发送 | 发送 Cookie |
AJAX | $.get("...") | 不发送 | 不发送 | 发送 Cookie |
Image | <img src="..."> | 不发送 | 不发送 | 发送 Cookie |
Secure,HttpOnly
Secure 属性限制该 Cookie 只有在 HTTPS 协议下,才会被发送到服务器。若当前协议是 HTTP,则浏览器会忽略服务器发来的 Secure 属性。
HttpOnly 属性指定该 Cookie 无法通过 JavaScript 脚本获取到,防止恶意脚本的攻击。
Cookie 与跨域、安全
在发送跨域 AJAX 请求时,Cookie 默认不会被发送。如果需要允许跨域请求携带 Cookie ,需要在发起请求的时候设置 withCredentials 请求头,服务端需要在请求响应中配置 CORS 相关头部(Allow-cross-with-credentials:true)。
一个比较特殊的情况,如果跨域 AJAX 请求配置了 withCredentials,即使响应没有配置 CORS 相关头部而被浏览器拦截,响应中的 set-cookie 也会生效,可以成功种上 Cookie。感兴趣的同学可以看看小伙伴的经历,详细规定可以参见规范。
但是在 img 标签、script 标签的 src 属性发起跨域请求时,请求会携带 Cookie。表单提交跨域请求也会带上 Cookie。CSRF 攻击就是通过这些方式实现的。通过配置 Cookie 的 SameSite 属性,可以防范 CSRF 攻击。而避免 XSS 攻击造成损失,敏感的 Cookie 要设置 secure、httpOnly属性。
推荐两篇前端安全相关的文章:
最后,梳理一下请求会携带 Cookie 的条件
- domain 跟 path 属性与请求的域名路径匹配,Cookie 才会被携带
- 如果有 Secure 属性,请求协议必须是 HTTPS
- 跨域 AJAX 请求不会携带 Cookie,如果需要携带需要配置 withCredentials 请求头以及 CORS 响应头
- 如果请求跨站了,还要判断 SameSite 属性是否满足发送条件
参考
浏览器系列之 Cookie 和 SameSite 属性, by 冴羽
HTTP cookies, by MDN
Cookie, by 阮一峰