目录
前言
一个插件 js-cookie
cookie 与 sessionStorage、localStorage 的对比 请 参见此文 的相关小节。
Cookie 用来:存储客户端的 HTTP 状态信息。
Cookie 的特点:
cookie 由浏览器保存的以下几块信息构成:
设置了 secure 和没设置 secure 的区别:
设置了 secure 之后,cookie 信息只能采用 SSL 的 https 协议发送给服务器,而 http 协议下就发送不了了。所以下面的例子中,“赵云”成功写入了 cookie 信息,而“韩信”却失败了。
document.cookie = "zyCookie=赵云";
document.cookie = "hxCookie=韩信; secure";
cookie 的每一段信息都是 Set-Cookie 头的一部分,他们使用“; ”分隔每一段信息,其中只有 cookie 的名字和值是必须的。
下面是一段带有 cookie 信息的请求头:
/* 访问 https://www.test.com 的请求头 */
HTTP/1.1 200 0K
Content-type: text/html
Set-Cookie: name=myCookie; path=/; domain=.test.com; expires=Mon, 22-Jan-27 18:10:17 GMT; secure
Other-header: other-header-value
根据上述代码,分析一下这条 cookie:
基本的 cookie 操作有三种:读取、写入 和 删除。
下面封装了一个 JavaScript 操作 cookie 的对象:
var CookieUtil = {
// 读取 cookie
get: function(name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null,
cookieEnd;
if(cookieStart > -1){
cookieEnd = document.cookie.indexOf(";", cookieStart);
if(cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
}
return cookieValue;
},
// 写入 cookie
set: function(name, value, expires, path, domain, secure){
var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);
if(expires instanceof Date){
cookieText += "; expires=" + expires.toGMTString();
}
if(path){
cookieText += "; path=" + path;
}
if(domain){
cookieText += "; domain=" + domain;
}
if(secure){
cookieText += "; secure";
}
document.cookie = cookieText;
},
// 删除 cookie
unset: function(name, path, domain, secure){
this.set(name, "", new Date(0), path, domain, secure);
}
}
使用 CookieUtil 对象时注意:
CookieUtil.set();
CookieUtil.set("test01");
正确使用 CookieUtil 对象的栗子:
// 写入 cookie
CookieUtil.set("test02", "two");
CookieUtil.set("test03", "three", new Date("2020.06.17 20:00:00"), "/", "localhost");
// 读取 cookie
console.log(CookieUtil.get("test02")); // two
console.log(document.cookie); // test02=two; test03=three
// 删除 cookie
CookieUtil.unset("test02");
CookieUtil.unset("test03");
还未到失效时间的 cookie 是删不掉的,到期后浏览器会自动删除。
子 cookie 就是使用 cookie 值来存储多个 cookie。它的作用是——绕开浏览器对单个域名下 cookie 数量的限制。
如果你担心开发中可能会达到单域名的 cookie 上限,那么子 cookie 可能是一个非常有吸引力的备选方案。不过,你需要更加密切地关注 cookie 的长度,以防超过单个 cookie 的长度限制。
子 cookie 与 cookie 的常见格式对比:
// 子 cookie
name=name1=value1&name2=value2&name3=value3
// cookie
name=value
下面封装了读取、写入和删除子 cookie 的代码:
var SubCookieUtil = {
/**
* 获取子 cookie
*/
get: function(name, subName){
var subCookies = this.getAll(name);
if(subCookies){
return subCookies[subName];
}else{
return null;
}
},
getAll: function(name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
cookieEnd = "",
subCookies = "",
i = 0,
parts = [],
result = {};
if(cookieStart > -1){
cookieEnd = document.cookie.indexOf(";", cookieStart);
if(cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd);
if(cookieValue.length > 0){
subCookies = cookieValue.split("&");
for(i, len=subCookies.length; i 0 && subcookies.hasOwnProperty(subName)){
subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName]));
}
}
if(subcookieParts.length > 0){
cookieText += subcookieParts.join("&");
if(expires instanceof Date){
cookieText += "; Expires=" + expires.toGMTString();
}
if(path){
cookieText += "; path=" + path;
}
if(domain){
cookieText += "; Domain=" + domain;
}
if(secure){
cookieText += "; Secure";
}
}else{
cookieText += "; expires=" + (new Date(0)).toGMTString();
}
document.cookie = cookieText;
},
/**
* 删除子 cookie
*/
unset: function(name, subName, path, domain, secure){
var subcookies = this.getAll(name);
if(subcookies){
delete subcookies[subName];
this.setAll(name, subcookies, null, path, domain, secure);
}
},
unsetAll: function(name, path, domain, secure){
this.setAll(name, null, new Date(0), path, domain, secure);
}
}
由上述代码可知:
当获取子 cookie 时,用 get() 方法获取单个 cookie 的值,用 getAll() 方法获取所有子 cookie 并将它们放入一个对象中返回,对象的属性为子 cookie 的名称,对应值为子 cookie 对应的值。其实,get() 方法就是调用 getAll() 获取所有的子 cookie,然后只返回所需要的那一个(如果 cookie 不存在,就返回 null)。getAll() 方法和 get() 方法在解析 cookie 值的方式上非常相似。区别在于 cookie 的值并非立即解码,而是先根据 & 字符将子 cookie 分割出来放在一个数组里,每一个子 cookie 再根据等于号分割,这样在 parts 数组中的前一部分便是子 cookie 名,后一部分便是子 cookie 值。这两项都要使用 encodeURIComponent() 方法来解码,然后放入 result 对象中,最后最为方法的返回值。如果 cookie 不存在,则返回 null。
当设置(写入)子 cookie 时,用 set() 方法获取指定 cookie 名称的所有子 cookie。当 getAll() 返回 null 时,用逻辑或操作符“||” 将 subcookies 设置为一个新对象。然后,在 subcookies 对象上设置好子 cookie 值并传给 setAll() 方法。然后,setAll() 方法使用 for-in 循环遍历第二个参数中的属性。然后,使用 hasOwnProperty() 方法确保只有实例属性被序列化到子 cookie 中。由于可能会存在属性名为空字符串的情况,所以把属性名加入结果对象之前还要检查一下属性名的长度。将每个子 cookie 的名值对儿都存入 subcookieParts 数组中,以便稍后可以使用 join() 方法以 & 号组合起来。剩下的步骤就和 CookieUtil.set() 一样了。
当删除子 cookie 时,普通的 cookie 可以通过将失效时间设置为过去的时间的方法来删除,但是子 cookie 不能这样做。为了删除一个子 cookie,首先必须获取包含在某个 cookie 中的所有子 cookie,然后,仅删除需要删除的那个子 cookie,然后再将余下的子 cookie 的值保存为 cookie 的值。基于此,用 unset() 方法删除某个 cookie 中的单个子 cookie 而不影响其他的 cookie;而 unsetAll() 方法等同于 CookieUtil.unset() 方法,用于删除整个 cookie。
// 逐个设置子 cookie
SubCookieUtil.set("data01", "heroname", "Sunwukong");
SubCookieUtil.set("data01", "heroAttribute", "assassin warrior");
// 一次设置多个子 cookie
SubCookieUtil.setAll("data02", {heroname:"Machao", heroAttribute:"assassin"}, new Date("2020.06.18 17:00:00"));
// 获取某个 cookie 里的单一子 cookie
console.log(SubCookieUtil.get("data02", "heroname")); // Machao
console.log(SubCookieUtil.get("data02", "heroAttribute")); // assassin
// 获取某个 cookie 里的全部子 cookie
console.log(SubCookieUtil.getAll("data01")); // {heroname: "Sunwukong", heroAttribute: "assassin warrior"}
// 逐个删除子 cookie
SubCookieUtil.unset("data01", "heroAttribute");
// 删除全部子 cookie
SubCookieUtil.unsetAll("data02");
cookie 用来存储大小在 4KB 以内的小数据。当 cookie 的大小超过最大限制时,该 cookie 会被浏览器默默地丢弃掉。
不同浏览器对 cookie 的个数要求不同:
cookie 在性质上是绑定在特定的域名之下的。当设定了一个 cookie 后,再给创建它的域名发送请求时,都会包含这个 cookie。这个限制确保了储存在 cookie 中的信息只能让批准接受者(同源者)访问,而无法被其他域访问(不能跨域)。
在写入 cookie 时遇到 samesite 跨域问题。
原因:当 cookie 中没有设置 samesite 属性时,Chrome80 默认设置了 samesite=lax。
解决方案:
第一步:后端设置 samesite=None
用 Ajax 请求时,在后端这样设置:
response.setHeader(“Set-Cookie”, “HttpOnly;Secure;SameSite=None”);
设置 samesite=none 的同时必须设置 secure,不设置 secure 无效,而设置 secure 之后这样设置后就只能采用“https”与服务器通信了。
第二步:禁用 Chrome 的 SameSite 功能
samesite 的设计初衷是为了防止 CSRF 攻击,禁用 samesite 实际上并没有解决问题,属于下下策。
进入这个网站:chrome://flags/
然后,找到 SameSite,将找到的三个功能均设置为“Disabled”,最后点击 reLaunch 按钮重启 Chrome。
有更好的方案欢迎大家留言。
看情况:
【推荐阅读】
cookie、token 和 session