# 从 Cookie 说起

  • 存储在浏览器端的一串字符串(最大 5kb)
  • 跨域不共享。浏览器可为每个域名以及域名下的 每个 path 存储一个 cookie,各域名之间不共享,path 下的 cookie 权限,小范围可访问大范围。
  • 结构化数据:key1=val1; key2=val2; key3=val3; 注意是通过 '; '连接的
  • 浏览器环境下可以修改 cookie,但是可以进行限制,限制的目的也是为了不让修改 cookie
  • 每次的 Http 请求会自动将当前请求域的 cookie 一起发送到服务端
  • server 端可获取并修改 cookie 然后返回浏览器
    • 获取:cookie 到了服务端默认在 req.headers.cookie 中
    • 设置:res.setHeader('Set-Cookie', key=value; path=/; httpOnly; expires=时间)
    • path 不设置时,会默认将 cookie 设置到当前访问的 path 下
    • httpOnly 的设置是限制 cookie 在浏览器端不能被修改的
    • expires 是设置 cookie 到期的时间,要通过 toGMTString() 方法将时间转成 GMT 形式
  • 获取 cookie: document.cookie
  • 设置 cookie: document.cookie = 'key1=value1' 会自动使用字符串方式拼接到现有 cookie 上

测试时,可以手动在浏览器端通过 document.cookie 添加 cookie,然后刷新页面尝试

const http = require("http");

const server = http.createServer((req, res) => {
  // cookie 默认存在 req.headers.cookie
  // 形式 k1=v1; k2=v2;

  // 将 cookie 存储在 req 中
  req.cookie = {};

  const cookieStr = req.headers.cookie || "";
  cookieStr.split(";").forEach((item) => {
    const [key, val] = item.split("=");
    req.cookie[key] = val;
  });

  res.end(JSON.stringify(req.cookie));
});

server.listen(7788, () => {
  console.log("server is running at port 7788");
});
  • cookie 在服务端的设置是通过 res.setHeader 中的 Set-Cookie 来处理的
const http = require("http");
const querystring = require("querystring");

const server = http.createServer((req, res) => {
  res.setHeader("Content-type", "application/json; text/plain; charset=utf-8");

  // cookie 默认存在 req.headers.cookie
  // 形式 k1=v1; k2=v2;

  // 将 cookie 存储在 req 中
  req.cookie = {};
  const cookieStr = req.headers.cookie || "";
  cookieStr.split(";").forEach((item) => {
    const [key, val] = item.split("=");
    req.cookie[key] = val;
  });

  // query 参数
  const [path, query] = req.url.split("?");
  req.path = path;
  req.query = querystring.parse(query);

  if (path === "/login-in") {
    const { username, password } = req.query;

    // 修改(设置) cookie
    // path 指明 cookie 设置的 path 路径,不设置的话,会默认设置到当前的 path 上
    res.setHeader("Set-Cookie", `user=${username}; path=/`);
    res.end(
      JSON.stringify({
        username,
        password,
        msg: "欢迎登录, cookie设置成了user=username",
      })
    );
    return;
  }

  // 登录后,浏览器端有了 cookie,再次发送请求会从 req.headers.cookie 中获取到
  if (path === "/login-test") {
    res.end(
      JSON.stringify({
        username: req.cookie.user,
        msg: "已登录,从请求中获取到了cookie.user",
      })
    );
  }
});

server.listen(7788, () => {
  console.log("server is running at port 7788");
});

cookie 本身可以在浏览器端通过 document.cookie 进行获取和修改。如果涉及登录信息,就没有任何安全了。所以需要对 cookie 的修改做限制,让它只能在服务端被修改。 这就需要在服务端返回设置 cookie 的 setHeader 中做处理了。

const http = require("http");
const querystring = require("querystring");

// 设置 cookie 过期时间
function setCookieExpires() {
  const d = new Date();
  // 在当前时间加上24小时
  d.setTime(d.getTime() + 24 * 60 * 60 * 1000);

  // toGMTString 的格式:Sat, 11 Dec 2021 04:51:51 GMT
  return d.toGMTString();
}

const server = http.createServer((req, res) => {
  res.setHeader("Content-type", "application/json; text/plain; charset=utf-8");

  // cookie 默认存在 req.headers.cookie
  // 形式 k1=v1; k2=v2;

  // 将 cookie 存储在 req 中
  req.cookie = {};
  const cookieStr = req.headers.cookie || "";
  cookieStr.split("; ").forEach((item) => {
    const [key, val] = item.split("=");
    req.cookie[key] = val;
  });

  // query 参数
  const [path, query] = req.url.split("?");
  req.path = path;
  req.query = querystring.parse(query);

  if (path === "/login-in") {
    const { username, password } = req.query;

    // 修改(设置) cookie
    // path 指明 cookie 设置的 path 路径,不设置的话,会默认设置到当前的 path 上
    // httpOnly 指明 cookie 只能在服务端修改
    // expires 设置 cookie 过期时间
    res.setHeader(
      "Set-Cookie",
      `user=${username}; path=/; httpOnly; expires=${setCookieExpires()}`
    );

    res.end(
      JSON.stringify({
        username,
        password,
        msg: "欢迎登录, cookie设置成了user=username",
      })
    );
    return;
  }

  // 登录后,浏览器端有了 cookie,再次发送请求会从 req.headers.cookie 中获取到
  if (path === "/login-test") {
    res.end(
      JSON.stringify({
        username: req.cookie.user,
        msg: "已登录,从请求中获取到了cookie.user",
      })
    );
  }
});

server.listen(7788, () => {
  console.log("server is running at port 7788");
});

经过上述在 Set-Cookie 中添加 httpOnly。当通过访问 localhost:7788/login-in?username=hzz&password=123 模拟登录后,再通过在浏览器控制台手动将 cookie 设置成别的,如:document.cookie='user=lisi', 再次访问 localhost:7788/login-test 校验登录信息查看时发现 cookie 保持为登录时的 hzz 不变。