谈谈对 cookie 的理解
前言 #
我们知道 HTTP 属于无状态协议,在客户端和服务端数据传输完毕之后就会断开,再次请求时需要重新建立连接;这就使得服务器无法确认两次请求是否属于同一个客户端。
Cookie 的诞生最初就是为了解决:网站为了辨别用户身份而储存在用户本地终端上的数据。
使用流程大致如下:
在新的用户请求时,服务器给它颁发一个身份证存储在浏览器 cookie 下,当发起 http 请求时,浏览器会检查 cookie 并自动添加到 request header
的 cookie 字段中传输到服务器,这样服务器就会知道是谁来访问了。
早期,cookie 也被广泛应用于用户登录认证使用。
一、cookie 的使用 #
1. 读取 cookie #
在前端,我们可以通过 document.domain
来获取当前网址域名,如掘金域名:'juejin.cn'
;
通过 document.cookie
可以获取该域下可以使用的所有 cookie
,得到的是一个字符串类型:
'a=1; b=2; c=3'
'a=1; b=2; c=3'
其中 ;
来分割每一个 cookie,=
来分割 cookie 的 key 和 value。
通常我们会编写工具方法来解析我们想要的 cookie:
function getCookie(target) {
const cookieList = document.cookie.split('; ');
let result = '';
for (let i = 0; i < cookieList.length; i++) {
if (cookieList[i].includes(target)) {
result = cookieList[i].split('=')[1];
break;
}
}
return result;
}
function getCookie(target) {
const cookieList = document.cookie.split('; ');
let result = '';
for (let i = 0; i < cookieList.length; i++) {
if (cookieList[i].includes(target)) {
result = cookieList[i].split('=')[1];
break;
}
}
return result;
}
注意,尽管可以拿到 cookie 的 name 和 value 信息,但无法获取 cookie 的 domain 和 path 信息。
2. 设置 cookie #
cookie 支持设置的属性有:
- expires 过期时间:
- 如果不设置,则止为
session
,即会话 cookie,在浏览器关闭后清除; - 如果设置一个已过期的时间,则 cookie 会被清除(删除动作);
- 设置有效期可以通过下面方式来设置:
new Date(Date.now() + 864000).toUTCString();
new Date(Date.now() + 864000).toUTCString();
- domain 和 path:
domain
是域名,path
是路径,两者结合构成了 URL,这两者一起使用来限制 cookie 能够被哪些 url 访问获取。
domain 和 path 两个选项共同决定了 cookie 何时被浏览器自动添加到请求头部中发送出去。如果没有设置这两个选项,则会使用默认值。domain 的默认值为设置该 cookie 的网页所在的域名,path 默认值为设置该 cookie 的网页所在的目录。
- httpOnly:
用来设置 cookie 是否能通过 js 去访问。默认情况下设置 cookie 不带该选项,客户端可以通过 js 代码去访问(包括读取、修改、删除等)cookie。
当 cookie 设置了 HttpOnly 选项时,客户端将无法通过 JS 代码去访问(包括读取、修改、删除等)这个cookie。
另外,在客户端是不能通过 JS 代码去设置一个 httpOnly 类型的 cookie,这种类型的 cookie 只能通过服务端来设置。
- 客户端设置 cookie:
document.cookie="key=value; expires=" + new Date(Date.now() + 864000).toUTCString() + "; domain=dogesoft.joywok.com; path=/";
document.cookie="key=value; expires=" + new Date(Date.now() + 864000).toUTCString() + "; domain=dogesoft.joywok.com; path=/";
注意,document.cookie 一次只能设置一个,如果你这样写:
document.cookie="key1=value1; key2=value2;"
,只会存储key1
到 cookie 中(第一个分号前面的信息视为 cookie 字段 key 和值),key2 会看作是 domain、path 这些配置属性,最终判做为无效属性。
- 服务端设置 cookie:
服务端在返回 response 时可以通过 set-cookie
来设置,支持设置的属性有:expires、domain、path、HttpOnly
。
3. 修改 cookie #
要想修改一个cookie,只需要重新赋值就行,旧的值会被新的值覆盖。这一点同「设置 cookie」
。
4. 删除 cookie: #
删除 cookie 的方式上面已经提到,设置为过期时间即代表删除操作:
document.cookie="key=value; expires=" + new Date(Date.now() + -864000).toUTCString() + "; ";
document.cookie="key=value; expires=" + new Date(Date.now() + -864000).toUTCString() + "; ";
二、不同 domain 相同 name 的 cookie 问题 #
在设置 cookie 时满足哪些条件可以实现覆盖更新 cookie 值呢?需要满足 name、domain、path
都一致时表示去更新同一个 cookit。
那会不会有相同 name 的 cookie,存在多份情况呢?当同名 cookie 的 domain 或 path
存在不一致时会出现多份。
且 domain
是有共享特性的,即:cookie 子域名可以读父域名中的 cookie。
如在 .ping.com
域下注入 cookie,则该子域下的网页如 p1.ping.com、p2.ping.com
都能读取到 cookie 信息。
这会带来一个什么问题?
假设你现在访问 p1.ping.com
网址,现在 .ping.com
和 p1.ping.com
下都有一个名称为 token
的 cookie。
不巧的是,其中有一个 token cookie 是失效的,而对于前端:
- 一是无法获取指定 domain 下的 cookie,因为
document.cookie
拿到的只有 name 和 value,无法得知 cookie 的 domain 和 path 信息; - 二是通过
document.cookie
拿到多个 cookie 信息后,无法知道哪个可以使用,哪个是失效的;
当我们拿失效的 token 去请求接口后,得到响应是登录失效信息,这并不是我们想要的。
应对办法:
- 如果你有类似场景:页面是通过后台
redirect
重定向接口跳转过来,并且 token cookie 也是由 redirect 接口去埋入。
这种场景可以让后台在埋入 cookie 前将 「一级 domain」和「二级 domain」都先清除掉,再进行 cookie 的埋入。
- 另一种场景就是你的应用属于子应用,token 是从别的地方埋入的,你这里只有读取的逻辑。
可以将所有同名的 token cookie 收集起来,交给后台来校验 cookie 是否有效。
三、关于 domain 设置的方式 #
- 如果需要显式设置 domain,只能设置在当前域名和父级(一级)域名下,其他 domain 域名将设置不成功;
- 如果没有显式设置,则浏览器会自动取
url 的 host
作为 domain 值; - 关于 domain value 前面带 . 点 与不带点的区别:
- 带点:允许 subdomain 访问;
- 不带点:只有完全一样的域名才能访问。
四、cookieStore #
Chrome 87 版本之后的浏览器,为我们提供了操作 cookie 的 API:cookieStore
,它是一个异步的 API,可以很方便的操作 Cookie。
并且原生 document.cookie 获取不到 cookie 的 domain 信息,它可以获取到。
使用时需要注意一点:只能在 https
协议下支持使用。
通常我们需要判断环境是否支持:
const isSupportCookieStore = location.protocol === 'https:' && typeof window.cookieStore === 'object';
const isSupportCookieStore = location.protocol === 'https:' && typeof window.cookieStore === 'object';
1. 读取 cookie #
- 获取单个 cookie:
await cookieStore.get('token');
await cookieStore.get('token');
- 获取所有 cookie:
await cookieStore.getAll();
// or
await cookieStore.getAll({ name: 'token' }});
// or
await cookieStore.getAll('token');
await cookieStore.getAll();
// or
await cookieStore.getAll({ name: 'token' }});
// or
await cookieStore.getAll('token');
2. 设置/更新 cookie #
await cookieStore.set('cegz', true);
await cookieStore.set({ name: 'cyl', value: true });
await cookieStore.set('cegz', true);
await cookieStore.set({ name: 'cyl', value: true });
3. 删除 cookie #
await cookieStore.delete({ name: 'token' });
await cookieStore.delete({ name: 'token' });
4. 监听 cookie 变化 #
cookieStore
还提供了监听 cookie 变化的方式:
cookieStore.addEventListener('change', event => {
console.log(event.changed, event.deleted);
});
cookieStore.addEventListener('change', event => {
console.log(event.changed, event.deleted);
});
由于 cookieStore 兼容性很弱,再加上只能运行在 https 协议上,使得我们在真实业务上很难去投入生产使用,这里仅作为了解。
版权属于: vincent
转载时须注明出处及本声明