Cookie教程-全面了解Cookie

126 阅读9分钟
HTTP Cookies
由于Cookie的内容较多,本文采用渐进式的讲解方式,一方面是为了方便正在紧张工作中想立即获取信息的人;另一方面是为了方便想系统学习Cookie知识的人能得到他们想要的。

删除Cookie

function deleteCookie(name, path, domain, secure) {
    let  date = new Date();
    date.setTime(date.getTime() - 1);
    document.cookie = [
        encodeURIComponent(name), '=', 
        '; expires=' , date.toUTCString(),
        path? ('; path=' + path): '',
        domain? ('; domain=' + domain): '',
        secure? '; secure': ''
    ].join('');
}

设置Cookie

function setCookie(name, value, expiresTimestamp, path, domain, secure) {
  const date = new Date(expiresTimestamp);
  document.cookie = [
    encodeURIComponent(name), '=', encodeURIComponent(value),
      '; expires=' , date.toUTCString(),
      path? ('; path=' + path): '',
      domain? ('; domain=' + domain): '',
      secure? '; secure': ''
  ].join('');
}

获取Cookie

function getCookie(key) {
  let result = key ? undefined : {},
     cookies = document.cookie ? document.cookie.split('; ') : [],
     i = 0,
     l = cookies.length;

  for (; i < l; i++) {
    let parts = cookies[i].split('='),
      name = decodeURIComponent(parts.shift()),
      cookie = parts.join('=');

    if(cookie.indexOf('"') === 0) {
      cookie = cookie.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
    }
    try {
      cookie = decodeURIComponent(cookie.replace(/\+/g, ' '));
    } catch(e){}
    if (key === name) {
      result = cookie;
      break;
    }

    if (!key && (cookie !== undefined)) {
      result[name] = cookie;
    }
  }
  
  return result;
}

Cookie是什么

Cookie其实就是Web服务保存在浏览器端的一小段标记,这段标记可以跟随Http数据一起发送到服务端。

Javascript操作Cookie的API

Javascript设置Cookie

假设URL为www.itthink.tech/login 的服务想在浏览器端种下登录token和用户唯一标识uid,它们的值分别是abc789659hjr735 hr6gac25gfs6dw56,对这两个Cookie有这样的要求:

  1. 分别在浏览器中保存一个月和半年
  2. 所有itthink.tech的子域名都可以访问这两个Cookie
  3. 所有路径都可以访问这两个Cookie
  4. 仅在Https协议下才可发送到服务端。
  5. 不可作为第三方Cookie发送到服务端

我们借此例来展示设置Cookie的API:

const oneMonth = new Date(Date.now() + (1000 * 60 * 60 * 24 * 30));
const oneMonthUTCString = oneMonth.toUTCString();
const sixMonth = new Date(Date.now() + (1000 * 60 * 60 * 24 * 30 * 6));
const sixMonthUTCString = oneMonth.toUTCString();
// 设置token
document.cookie = 'token=abc789659hjr735; domain=.itthink.tech; path=/; expires=' + oneMonthUTCString + '; secure; samesite=lax';
// 设置uid
document.cookie = 'uid=hr6gac25gfs6dw56; domain=.itthink.tech; path=/; expires=' + sixMonthUTCString + '; secure; samesite=lax';

可以看到document.cookie被赋值两次,每一次document.cookie只能设置一个Cookie,不能把所有Cookie一次性赋值给document.cookie

Javascript读取Cookie

读取document.cookie便可以获取到当前环境下允许获取的Cookie。和设置时每次只能设置一个Cookie不同,读取时是一次性读取出所有的Cookie的名字和值,但是无法读取出每个Cookie的属性。继续上面的示例:

console.log(document.cookie);
// 打印:token=abc789659hjr735; uid=hr6gac25gfs6dw56

可以看到,当读取document.cookie时,可以一次性读取出所有的Cookie名字和值,但是无法获取到属性。 这个是Cookie迷惑人的地方,虽然看起来cookiedocument的属性,但是它其实是getter和setter。

Javascript操作Cookie的API就是这些了。

Http操作Cookie的API

如果服务端想向浏览器端写Cookie,只有通过Http。

Http设置Cookie

假设URL为www.itthink.tech/login 的服务想在浏览器端种下两个Cookie, Cookie 1: 名字:token 值:abc789659hjr735 保存时间:一个月,假设一月后的时间是 所属域:所有itthink.tech和其子域名都可以访问这个Cookie 所属路径:所有路径都可以访问这个Cookie 安全:仅在Https协议下才可发送到服务端 安全:禁止Javascript访问和修改该Cookie 安全:不可作为第三方Cookie发送到服务端

Cookie 2: 名字:uid 值:hr6gac25gfs6dw56 保存时间:半年 所属域:所有itthink.tech和其子域名都可以访问这个Cookie 所属路径:所有路径都可以访问这个Cookie 安全:仅在Https协议下才可发送到服务端 安全:禁止Javascript访问和修改该Cookie 安全:不可作为第三方Cookie发送到服务端

要种上面两个Cookie,通过Http来设置的话,需要在响应头中加上如下内容: 假设已有响应头:

HTTP/2.0 200 OK
Content-Type: text/html

要种Cookie的话则变成:

HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: token=abc789659hjr735; domain=.itthink.tech; path=/; expires=Thu, 10 Nov 2022 02:58:20 GMT; secure; httponly; samesite=lax
Set-Cookie: uid=hr6gac25gfs6dw56; domain=.itthink.tech; path=/; expires=Sun, 09 Apr 2023 02:59:09 GMT; secure; httponly; samesite=lax

可以看到Set-Cookie后面的格式和设置document.cookie的格式一致。 服务端开发人员一般不太需要像这样去设置Cookie,服务器都会提供对应的API。

Http读取Cookie

Cookie由浏览器负责存储,当有符合条件的请求时,浏览器会将Cookie放入Http请求头中,继续使用上面的例子,Http请求头如下:

Cookie: token=abc789659hjr735; uid=hr6gac25gfs6dw56

可以看到,和Javascript的document.cookieAPI一样,Http中可以拿到所有允许获取的Cookie的名字和值,但是无法获取到Cookie的属性。

XMLHttpRequest(XHR)和Fetch不能读取和操作Cookie

有时我们可能希望通过XHR或者Fetch直接往Http请求中塞入Cookie,这个是不可以的。这一点确实会比较让人迷惑,XHR和Fetch都有设置Header的API,而且Cookie属于Header的一部分,你甚至可以设置自定义的Header,但是就是不能设置Cookie。 有时我们可能希望从XHR或者Fetch的Response中获取Cookie,这也是不可以的。我们可以从Response中获取除Set-Cookie之外的任何Header。

这样做可以确保在正常情况下,服务器端得到的Cookie一定是浏览器中真正的Cookie,不可以用Javascript模拟。服务端向浏览器中设置的Cookie一定是经过浏览器处理后通过document.cookie来获取。

以上就是操作Cookie的所有API了。

Cookie特性详解

大家照着上面的例子去写Cookie相关的代码,代码的运行结果很有可能和你预期不一样,这时候往往是Cookie的特性在作怪。

如果你对Cookie一无所知,那可以先看一下下面的图: image 这幅图说明了三个事实:

  1. Cookie可以通过Http响应来种到浏览器中
  2. Cookie可以通过Javascript种到浏览器中
  3. Cookie可以跟随Http请求发送到服务端

相信如果你没有专门研究过Cookie,那对它的了解应该就是这些了,这些确实就是Cookie最核心的功能,但是要让核心功能安全放心的运行,需要有各方面的考虑:

  1. 给Cookie设计一种数据格式
  2. 设计操作Cookie的API
  3. 由于浏览器环境的特殊性,需要考虑数据的隔离、生命周期与安全

上面已经大概讲过了Cookie的API的大体形式,下面咱们说一下数据格式和具体的属性。

Cookie数据是以Name Value键值对的形式进行组织的,Name即某个Cookie的名字,另外和每个Cookie相关的还有若干属性,这些属性用来设定具体Cookie的隔离性、生命周期、安全性。

每个Cookie键值对都可以有如下属性,点击查看详细介绍: domain path expires max-age httponly secure

以下新属性由谷歌推出,还没有成为正式规范,但已经被主流浏览器广泛实现: samesite

以下新属性由谷歌推出,还没有成为正式规范,只有较新的谷歌浏览器才支持: sameparty partitioned

以下属性已经由谷歌提出多年,但是并没有被其它浏览器广泛的支持 priority

以上这些属性是我们理解Cookie的关键。咱们一一道来。

Cookie标准

在Web世界,技术的发展主要由几大IT厂商和一些标准组织主导,为了更好的互联互通,IT厂商会遵从标准协议来进行产品的开发。W3C负责HTML、CSS的标准化,ECMA国际负责Javascript的标准化。而我们今天要说的Cookie其标准除了第一版非正式标准由网景公司制定外,后续的版本均是由IETF组织制定,IETF先后经历了RFC2109 RFC2965,到2011年的RFC6265,现在最新版的草案已经于2020年由谷歌提出。RFC6265是目前被广泛实现的Cookie标准。

IETF虽然制定了规范,但是各个浏览器厂商并不一定会严格按照规范执行。

Cookie的历史

1994年,Cookie最初是网景公司的员工开发,目的是为了开发电子商务网站的购物车功能,有了Cookie就可以对用户进行标识,从而记录他想买哪些商品。 所以Cookie一开始就是为了追踪用户而设计的,而且当时所有的浏览器都会静默地接收Cookie,Cookie被默默地用于夸站点用户追踪,用户毫不知情。直到1996年,Cookie的存在被曝光,由于Cookie被用来跨站点追踪用户而导致用户隐私泄漏的风险,在后来的几年里,Cookie机制受到了密集地审查。 这就直接促使IETF(Internet Engineering Task Force)开始制定正式的Cookie规范。IETF想禁用第三方Cookie,除非用户选择允许使用。 但是领头的浏览器厂商都没有听从这个建议,第三方Cookie一直可以被使用。 因此Cookie从诞生开始就伴随着用户隐私泄漏的问题,不过它也确实让广告业变得更兴盛。