Cookie 的使用

目录

前言

一、cookie 的结构

二、JS 操作 cookie

1、写入 cookie

2、读取 cookie 

3、删除 cookie

三、子 cookie

1、写入子 cookie

2、获取子 cookie

3、删除子 cookie

四、cookie 的局限性

五、解决 cookie 的跨域

1、关闭浏览器后 cookie 会消失吗?


前言

一个插件 js-cookie

cookie 与 sessionStorage、localStorage 的对比 请 参见此文 的相关小节。

Cookie 用来:存储客户端的 HTTP 状态信息。

Cookie 的特点:

  • 同一个域名下的 cookie 是共享的。不利于 http 性能的提升,而且不同域名间会产生跨域。
  • 同步存储。
  • 数量受限,大小受限。不同的浏览器对 cookie 都有各自的数量限制,且每个 cookie 只能存储 4KB 大小的数据。
  • 可以设置有效期。关闭浏览器后,没有设置有效期的 cookie 会被清掉,设置了有效期的 cookie 会继续生效,直到过期时自动清掉。

一、cookie 的结构

cookie 由浏览器保存的以下几块信息构成:

  • 名称(name):唯一的 cookie 名,必须被 URL 编码(关于 URL 编码请戳:https://blog.csdn.net/mChales_Liu/article/details/106660255)。
     
  • 值(value):储存在 cookie 中的字符串值,必须被 URL 编码。
     
  • 域(domain):cookie 有效的域。比如:“http://www.test.com”。
     
  • 路径(path):指定域中的某个路径可以向服务器发送 cookie,一旦指定,别的路径都不能发送 cookie 了,即使是同域名。比如:“http://www.test.com/books”。
     
  • 失效时间(expires):表示 cookie 何时被删除的时间戳。默认情况下,浏览器会话结束时即将所有 cookie 都删除了;不过也可以自己设置删除时间,因此,cookie 可在浏览器关闭后依然保存在用户的机器上。
     
  • samesite:主要用来预防“跨站点请求伪造攻击(CSRF)”的,可以设置的值有三个:
    • strict:完全禁止第三方获取 cookie,跨站点时,任何情况下都不会发送 cookie;
    • lax:Chrome 默认值。大多数情况下禁止获取 cookie,除非导航到目标网址的 GET 请求(链接、预加载、GET表单);
    • none:没有限制。
       
  • 安全标志(secure):指定后,cookie 只有在使用 SSL 链接( https 访问)时才能被发送到服务器。若 cookie 中设置了 samesite=none,必须设置 secure。

设置了 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:

  • 名称——请求头中指定了一个叫做 name 的 cookie;
  • 值——值为 myCookie;
  • 路径——对于由 path 指定域名下的所有页面都有效(这里“/”指的是整个域“www.test.com”(www 可省略));
  • 域——域为 .test.com;
  • 有效期——它会在 2027年1月22号 18:10:17 失效;
  • 安全标志——这个 cookie 设置了安全标志 secure。

二、JS 操作 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 对象时注意:

  • 此处,我开启了本地服务器(Wampserver)
  • 写入 cookie 时,name 和 value 是必须要传的,否则会添加一个 name 或 value 值是 undefined 的 cookie。
CookieUtil.set();
CookieUtil.set("test01");

 Cookie 的使用_第1张图片

正确使用 CookieUtil 对象的栗子:

1、写入 cookie

// 写入 cookie
CookieUtil.set("test02", "two");
CookieUtil.set("test03", "three", new Date("2020.06.17 20:00:00"), "/", "localhost");

2、读取 cookie 

// 读取 cookie
console.log(CookieUtil.get("test02"));        // two
console.log(document.cookie);                 // test02=two; test03=three

3、删除 cookie

// 删除 cookie
CookieUtil.unset("test02");
CookieUtil.unset("test03");

 还未到失效时间的 cookie 是删不掉的,到期后浏览器会自动删除。

三、子 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){
            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。

1、写入子 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"));

2、获取子 cookie

// 获取某个 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"}

3、删除子 cookie

// 逐个删除子 cookie
SubCookieUtil.unset("data01", "heroAttribute");
// 删除全部子 cookie
SubCookieUtil.unsetAll("data02");

四、cookie 的局限性

cookie 用来存储大小在 4KB 以内的小数据。当 cookie 的大小超过最大限制时,该 cookie 会被浏览器默默地丢弃掉。

不同浏览器对 cookie 的个数要求不同:

  • IE6 及其以下版本限制每个域最多 20 个 cookie。
  • IE7 及其以上版本和 Firefox 限制每个域最多 50 个 cookie。
  • Opera 限制每个域最多 30 个 cookie。
  • Safari 和 Chrome 对每个域的 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 的使用_第2张图片

有更好的方案欢迎大家留言。

1、关闭浏览器后 cookie 会消失吗?

看情况:

  • 若此时 cookie 没有持久化,浏览关闭后 cookie 会消失;
  • 若此时 cookie 进行了持久化,浏览器关闭后 cookie 不会消失。

【推荐阅读】

cookie、token 和 session

你可能感兴趣的:(JavaScript,计算机与网络,代码优化之道,Cookie,session,localStorage,IndexedDB)