# 浏览器存储-Cookie

Cookie 是直接存储在浏览器中的一小串数据。它们是 HTTP 协议的一部分,由 RFC 6265 规范定义。

Cookie 通常是由 Web 服务器使用响应 Set-Cookie HTTP-header 设置的。

最常见的用处之一就是身份验证:

  1. 登录后,服务器在响应中使用 Set-Cookie HTTP-header 来设置具有唯一“会话标识符(session identifier)”的 cookie。
  2. 下次如果请求是由相同域发起的,浏览器会使用 Cookie HTTP-header 通过网络发送 cookie。
  3. 所以服务器知道是谁发起了请求。

document.cookie 的值由 name=value 对组成,以 ; 分隔。每一个都是独立的 cookie。

console.log(document.cookie);
// _ga=GA1.2.1715548881.1637222237;
// _ym_d=1637222238;
// _ym_uid=1637222238233148688;
// _gid=GA1.2.1221244457.1637542038;
// _ym_isad=1; _ym_visorc=w

我们可以写入 document.cookie。但这不是一个数据属性,它是一个访问器(getter/setter)。对其的赋值操作会被特殊处理。

对 document.cookie 的写入操作只会更新其中提到的 cookie,而不会涉及其他 cookie。

document.cookie = "user=John"; // 只会更新名称为 user 的 cookie

# expiresmax-age

默认情况下,如果一个 cookie 没有设置这两个参数中的任何一个,那么在关闭浏览器之后,它就会消失。此类 cookie 被称为 "session cookie”。

// 当前时间 +1 天
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;

# httpOnly

这个选项和 JavaScript 没有关系,Web 服务器使用 Set-Cookie header 来设置 cookie。并且,它可以设置 httpOnly 选项。

这个选项禁止任何 JavaScript 访问 cookie。我们使用 document.cookie 看不到此类 cookie,也无法对此类 cookie 进行操作。

# 封装函数

获取 cookie 最简短的方式是使用 正则表达式。关键就在于匹配 name=;name=

getCookie(name) 函数返回具有给定 name 的 cookie:

// 返回具有给定 name 的 cookie,
// 如果没找到,则返回 undefined
function getCookie(name) {
  //
  let matches = document.cookie.match(
    new RegExp("(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)")
  );
  return matches ? decodeURIComponent(matches[1]) : undefined;
}

要删除一个 cookie,我们可以给它设置一个负的过期时间来调用它:

function deleteCookie(name) {
  setCookie(name, "", {
    "max-age": -1
  });
}

# XSRF 攻击

想象一下,你登录了 bank.com 网站。此时:你有了来自该网站的身份验证 cookie。你的浏览器会在每次请求时将其发送到 bank.com,以便识别你,并执行所有敏感的财务上的操作。

现在,在另外一个窗口中浏览网页时,你不小心访问了另一个网站 evil.com。该网站具有向 bank.com 网站提交一个具有启动与黑客账户交易的字段的表单 <form action="https://bank.com/pay"> 的 JavaScript 代码。

你每次访问 bank.com 时,浏览器都会发送 cookie,即使该表单是从 evil.com 提交过来的。因此,银行会识别你的身份,并执行真实的付款。

这就是“跨网站请求伪造(Cross-Site Request Forgery,简称 XSRF)”攻击。

当然,实际的银行会防止出现这种情况。所有由 bank.com 生成的表单都具有一个特殊的字段,即所谓的 “XSRF 保护 token”,恶意页面既不能生成,也不能从远程页面提取它(它可以在那里提交表单,但是无法获取数据)。并且,网站 bank.com 会对收到的每个表单都进行这种 token 的检查。

但是,实现这种防护需要花费时间:我们需要确保每个表单都具有 token 字段,并且还必须检查所有请求。