导读
一、 简介
首先要知道标准的 http 协议是无状态的,也就是指服务端对于客户端的每次请求都认为它是一个新的请求,上一次会话和下一次会话之间是没有任何联系,这时当用户访问服务端并进行登录后,客户端之后的请求服务端依然无法对客户端身份进行识别, 如果将客户端与服务器之间的多次交互当做一个整体来看,那么服务端若想识别客户端的身份那么就需要将多次交互所涉及的数据(状态)保存下来。 cookie 的作用就是对请求和响应的状态进行一个管理, 服务端通过在响应体中设置 cookie (状态), 客户端会将 cookie (状态) 存储起来, 之后客户端的每个请求都将 cookie (状态)带上, 这样服务端就能够对客户端的身份进行识别;在早期 cookie 还未出现之前有个最简单的办法就是在请求的页面中插入一个 token,然后在下次请求时将这个 token 返回至服务器。这需要在页面的 form 表单中插入一个包含 token 的隐藏域,或者将 token 放在 URL 的 query 字符串中来传递。这两种方法都需要手动操作,而且极易出错, 而 cookie 则不同一般情况下客户端会自动存储服务器发来的 cookie, 并在之后的每次请求中自动带上 cookie 而无需客户端进行手动处理。
补充: 标准的http协议指的是不包括cookies, session,application的http协议,他们都不属于标准协议,虽然各种网络应用提供商,实现语言、web容器等,都默认支持它。
二、 基本原理
当浏览器向服务器发送请求的时候,服务器会将少量的数据以 set-cookie 消息头的方式发送给客户端, 浏览器一般会自动将 cookie 数据进行存储,当客户端再次访问服务器时,会将这些数据以 cookie 消息头的方式发送给服务器,服务端就可以根据 cookie 消息头来判别用户的身份或进行一些特别的处理并返回响应。
三、 生存期
从 cookie 的工作原理上可以知道 cookie 信息主要是存储在客户端的状态, 而 cookie 在客户端的生存期则主要由属性 max-age 决定, max-age 属性以秒为单位表示 cookie 的存活时间为 max-age 秒; 当然 cookie 还有一个属性 expires 其作用和 max-age 是一样的这在后续会进行详细说明。
- 默认情况下 cookie 只是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户关闭浏览器窗口后这些值也会随之销毁(生成的 cookie 临时存储于浏览器内存中)
- max-age 为正数: cookie 会在 max-age 秒之后被销毁(会将状态存储于 cookie 文件中并存储于本地硬盘中)
- max-age 为负数时: cookie 只在浏览器会话期间存在,当用户关闭浏览器窗口后这些值也会随之销毁(生成的 cookie 临时存储于浏览器内存中)
- max-age 为 0 时: cookie 将被立即销毁
四、 各大浏览器支持情况
IE 6 | IE 7.0 8.0 | Opera | Fire Fox | Safari | Chrome | |
---|---|---|---|---|---|---|
cookie 个数 | 每个域名下20个 | 每个域名下50个 | 每个域名30个 | 每个域名50个 | 没有限制 | 每个域名53个 |
cookie 大小 | 4095字节 | 4095字节 | 4096字节 | 4097字节 | 4097字节 | 4097字节 |
五、 属性介绍
上文说到 cookie 主要是用于管理服务端和客户端直接的状态, 其本质上就是一堆存储在客户端的数据, 每条 cookie 都有对应下面几个属性:
5.1 domain 和 path 属性
domain 指定了该 cookie 所属的域名,默认情况下,domain 会被设置为创建该 cookie 时所在的域名; 而 path 则指定了该 cookie 所属的路径; domain 和 path 两者一起来限制了该 cookie 允许被哪些 URL 访问, 当我们请求某个资源(URL)时只有当该 URL 域名能够同时被 domain 和 path 属性匹配时, 浏览器才会将此 cookie 添加到该请求的 cookie 头部中。
domain 的匹配是根据请求 URL 中的域名从后向前进行匹配, path 的匹配则是根据 URL 中的路径按照路径的匹配规则判断 URL 中的路径是否包含 path, 例如:
domain 属性 | path 属性 | 请求 URL | 请求是否包含 cookie | 描述 |
---|---|---|---|---|
auth.com | / | b.auth.com/a/d | 是 | - |
b.auth.com | /a | b.auth.com/a/b | 是 | - |
b.auth.com | /b | b.auth.com/a/b | 否 | path 属性不匹配,路径 /a/b 不包含 /b |
b.auth.com | /c | b.auth.com/a/b | 否 | path 属性不匹配,路径 /a/b 不包含 /c |
a.auth.com | / | b.auth.com/a/b | 否 | domain 不匹配 |
5.2 expires/max-age
首先需要知道清楚 expires 是 http/1.0 协议中的属性,在新的 http/1.1 协议之后 expires 已经由 max-age 选项代替,两者的作用都是限制 cookie 的有效时间。
expires 属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 cookie 。它的值是 UTC 格式,可以使用 Date.prototype.toUTCString() 进行格式转换。而 max-age 属性则是指定从现在开始 cookie 存在的秒数,比如 60 * 60 * 24 * 365
(即一年)。过了这个时间以后,浏览器就不再保留这个 cookie 。如果同时指定了 expires 和 max-age,那么 max-age 的值将优先生效。如果没有指定 expires 或 max-age 属性,那么这个 cookie 就是 Session Cookie, 即它只在本次对话存在,一旦用户关闭浏览器窗口, 浏览器将不会再保留这个 cookie 。
5.3 secure
该属性只是一个标记而没有值。包含 secure 选项的 cookie 只有在当请求是 HTTPS 或者其他安全协议时, 才能被发送至服务器。默认情况下, cookie 不会带 secure 选项(即为空)。所以默认情况下,不管是 HTTPS 协议还是 HTTP 协议的请求,cookie 都会被发送至服务端。但要注意一点,secure 选项只是限定了在安全情况下才可以传输给服务端,但并不代表你不能看到这个 cookie。同时需要注意的是, 如果想在客户端即网页中通过 js 去设置 secure 类型的 cookie,必须保证网页是 https 协议的。在http协议的网页中是无法设置 secure 类型 cookie 的。
5.4 httpOnly
httpOnly 背后的意思是告之浏览器该 cookie 绝不能通过 JavaScript 的 document.cookie 属性访问。设计该特征意在提供一个安全措施来帮助阻止通过 JavaScript 发起的跨站脚本攻击 (XSS) 窃取 cookie 的行为, 一旦设定这个标记,通过 documen.coookie 则不能再访问该 cookie。
5.5 sameSite
sameSite-cookies 是一种机制,用于定义 cookie 如何跨域发送,其目的主要是为了尝试阻止 CSRF (Cross-site request forgery 跨站请求伪造)以及 XSSI (Cross Site Script Inclusion (XSSI) 跨站脚本包含)攻击。
CSRF攻击简述: CSRF 攻击主要是盗用用户信息并发送恶意请求的一种攻击方式, 比如说某用户登录一个完全网站 A, A 站点返回一个能够标识用户身份的 cookie, 当用户无意中访问一个恶意网站 B, B 站点向 A 站点发起恶意请求, 这时请求中将会带上具有用户身份标识的 cookie。
XSSI攻击简述: XSSI 是 XSS 的一种形式, 假设网站 A 有一个脚本用于读取用户的私人账户信息, 攻击者可以在自己的恶意网站包含这个脚本, 当网站 A 的用户访问攻击者的网站时该网站将加载该脚本并带上用户身份标识 (cookie) 该脚本将读取用户的私人账户信息, 这时用户的信息可能就会发生泄露。
sameSite 属性可能值: 为 cookie 设置 sameSite 属性时需要给其定义一个值(如果没有设置值,默认是Strict),值可以是 Lax 或者 Strict
strict: 当 sameSite 属性值为 strict 时, 将禁止发送所有第三方链接的 cookies, 比如有 cookie ( domain = a.com, sameSite = strict ), 那么在其他网站请求 a.com 时都不会带上该 cookie
lax: 当 sameSite 属性值为 lax 时, 只会在使用危险 HTTP 方法发送跨域 cookie 的时候进行阻止,例如 POST 方式。比如有 cookie ( domain = a.com, sameSite = lax ), 那么在其他网站通过 POST 方式请求 a.com 时都不会带上该 cookie
六、 前端操作 cookie
6.1 创建 cookie
前端通过 document.cookie 设置 cookie, 设置多个 cookie 则需要编写多条 document.cookie = '...', 如果需要对 cookie 进行修改创建一条相同名称的 cookie 即可对 cookie 进行替换
// 创建多条 cookie 并设置 path 和 domain 以及 max-age 属性
document.cookie = 'name1=value1;path=/;domain=localhost;max-age=30';
document.cookie = 'name2=value2;path=/;domain=localhost;max-age=30';
document.cookie = 'name3=value3;path=/;domain=localhost;max-age=30';
/**
* 通用方法: 设置 cookie 方法
* @param {String} name cookie 名称
* @param {String} value cookie 值
* @param {Number} maxAge cookie 存活时间(maxAge秒, 默认存储 30 天)
*/
function setCookie({name, value, maxAge}) {
maxAge = maxAge || 30*24*60*60*1000;
// escape: 对字符串进行编码
document.cookie = `${name}=${escape (value)};max-age=${maxAge}`;
}
复制代码
通过浏览器(chrome)控制台查看 cookie 信息
6.2 获取 cookie
前端可直接通过 document.cookie 获取 cookie
// 打印 cookie
console.log(document.cookie); // name1=value1; name2=value2; name3=value3
/**
* 通用方法: 将 cookie 转为对象 {anme: value}
* @return {Object} {name, value}
*/
function getCookieObj() {
let cookieArr = document.cookie.split(";");
let obj = {};
cookieArr.forEach( v => {
let arr = v.split("=");
obj[arr[0]] = unescape(arr[1]);
});
return obj
}
复制代码
6.3 删除 cookie
前端并没有直接的 api 可以直接实现对 cookie 的删除, 可以通过将 cookie 的 max-age 属性设置 0 来实现对 cookie 的删除
// 删除 cookie
document.cookie = 'name3=value3;max-age=0';
/**
* 通用方法: 删除 cookie 方法
* @param {String} name cookie 名称
*/
function delCookie({ name }) {
document.cookie = `${name}=;max-age=0`;
}
复制代码
七、 服务端操作 cookie
7.1 设置 cookie
服务端设置 cookie 主要是通过设置响应头 set-cookie 来设置 cookie, 如果需要设置多个 cookie 得多写几个 set-cookie
// 1. koa 中直接通过设置请求头的方式设置 cookie
// 2. 在 node 中 response 响应中设置两次相同的 header 属性会发生合并, 可以通过一个数组来实现
ctx.set(
'Set-cookie',
'name=value;path=/user;domain=localhost;max-age=30;HttpOnly;SameSite=Strict'
);
复制代码
查看响应头
查看浏览器控制台
7.2 获取 cookie
服务器获取 cookie 实际上就是获取请求头 cookie
// koa 中获取请求头信息并进行打印(获取的 cookie 格式和前端获取到的格式是一样的所以也就可以通过相同的方式来处理)
console.log(ctx.request.headers.cookie); // name1=value1; name2=value2; name3=value3
复制代码
查看请求头信息