feng xiaohan

Cookie 和 Session

Cookie 是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。它通常是网站为了辨别用户身份储存在客户端(用户本地)上的数据(通常经过加密),是由用户客户端计算机暂时或永久保存的信息。

问题:express 所有的 render(redirect 等)都会发送一个新的请求,在执行 render 的中间件之前存储数据,可以让整个 render 的中间件里访问到这个数据。但是如果在 render 的中间件里写入数据,则每次执行该数据都会被刷新,无法存储。

  • 可以将数据存储为全局变量。但因为该变量在所有请求之间共享,所以也会在用户之间共享(不安全);
  • 使用 cookie。cookie 可以帮助我们将数据存储在单个用户的浏览器中,不会影响其他用户。

通过setHeader()Set-Cookie关键字来设置一个 cookie,以=分隔来设置 cookie 的 name 和 value:

res.setHeader("Set-Cookie", "name=value");

cookie 设置完以后,默认情况下浏览器会将它发送到服务器的每个请求

  • 设置多个 cookie

    我们可以同时设置多个 cookie 值:

    res.setHeader("Set-Cookie", "name=value; name2=666 ");
    
  • 设置 cookie 失效时长

    默认情况下的 cookie 在关闭浏览器后就会失效,我们可以 cookie 设置某个过期时间:

    res.setHeader(
      "Set-Cookie",
      "name=value; expires=" +
        new Date(new Date().getTime() + 86409000).toUTCString()
    );
    
  • 设置 cookie 的 Secure

    设置 Secure 意味着cookie 只有在页面通过 HTTPS 请求时才会被设置保护 cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。

    res.setHeader('Set-Cookie', 'name=value; Secure');
    
  • 设置 cookie 的 HttpOnly

    设置 HttpOnly 意味着我们无法通过客户端的 JS 访问和修改 cookie 的值(前端也可以使用 document.cookie 去获取和修改 cookie ),它保护 cookie 不被跨站脚本攻击窃取或篡改

    res.setHeader("Set-Cookie", "name=value; HttpOnly");
    

    跨域脚本攻击:客户端 JS 中可能被注入了恶意代码,如果没有限制客户端 JS 访问 cookie,可能会存在身份泄露等问题。

通过请求的get('Cookie')方法来获取一个 cookie:

req.get("Cookie"); // 获取的cookie为name=value形式,需要可以自己进行拆分

拆分 cookie:

req.get("Cookie").trim().split("=")[0]; // 获取cookie的name
req.get("Cookie").trim().split("=")[1]; // 获取cookie的value

我们可以从浏览器端修改 cookie 的值,所以 cookie 通常用于跨域请求存储数据(cookie 是存储在本地浏览器上,所以它可以发送到我们每个发起请求的页面,所以它可以从一个页面发送到另一个页面,即使这两个页面存在于不同的服务器上),但是一些敏感的数据不能让用户修改,这时候就可以使用 session:

Session

session 是当我们访问服务某个网页的时候,在服务器端的内存开辟一块内存。它存储在服务器的内存中,session 只有对应的浏览器才能访问。

客户端需要告诉服务器它对应的 session,因为 session 也是存储在内存或数据库中的一条条数据。

使用(实现)原理:

  • 我们使用cookie 来存储 session 的 ID,但实际上存储的值并不是 ID,而是经过哈希处理只有服务器可以识别的、以加密方式存储的 ID,这样在 cookie 中就存储了一个我们无法浏览器内部改变、较为安全的值;

  • 当用户保持当前浏览器的情况下再去访问服务器时,会把 session 的 ID 传给服务器,服务器根据 session 的 ID来为用户提供相应的服务。

在 Node(Express)中使用 Session

安装

使用时需下载express-session这个包:

npm install --save express-session

它是 Express 官方拓展包,但并不包含在 Express 里。

初始化 session 中间件

app.js

const express = require("express");
const session = require("express-session"); // 引入express-session

const app = express();

app.use(
  session({
    // 初始化设置session
    secret: "my secret", // 用于标记存储在cookie中的ID的hash
    resave: false, // session不会再每完成一个请求就保存 @1
    saveUninitialized: false, // 确保不为不需要保存的请求保存session
    // cookie: {} // 设置cookie相关配置
  })
);

@1:设置resave: false意味着当 session 发生变化时才会保存,有利于提高性能;

设置 session 的值

使用req.session.xxx = value;来定义存储一个 sessionID 的 cookie,cookie 的 value 为 session 加密的 ID:

exports.postLogin = (req, res, next) => {
  // res.setHeader('Set-Cookie', 'loggedIn=true; Secure');
  req.session.isLoggedIn = true; // 设置一个session
  res.redirect("/");
};

设置的 session 的名字是为了方便我们获取,其在 cookie 中存储的名字统称为connect.sid

获取 session 的值

使用req.session.xxx来获取一个 session 的值:

exports.getLogin = (req, res, next) => {
  console.log(req.session.isLoggedIn); // 获取session的值
};

存储 session 的值

通过req.session.xxx = value;设置的 session 是存储在内存中的,但内存是有限的,所以我们需要根据不同情况采用不同的存储方式。

对于开发者来说需要存储的用户的 session 较少,内存够用;但生产服务器可能会存储上千万的用户,每个用户都有自己的 session,这很快就会造成内存溢出。

  • 使用 MongoDB 数据库存储

    对于 express-session,需要下载connect-mongodb-session这个包:

    npm install --save connect-mongodb-session
    

    引入 connect-mongodb-session:

    app.js

    const session = require("express-session");
    const MongoDBStore = require("connect-mongodb-session")(session); // 实际上提供了一个需要传递session执行函数
    // ...
    const app = express();
    const store = new MongoDBStore({
      // 创建数据库存储
      uri: "mongodb+srv://<name>:<password>@cluster0.mnle1m2.mongodb.net/shop", // 数据库连接地址
      collection: "sessions", // 集合名称(如果不存在就创建)
    });
    
    app.use(
      session({
        secret: "my secret",
        resave: false,
        saveUninitialized: false,
        store: store, // 设置初始化存储
      })
    );
    

删除 session 的值

通过req.session.destroy()来删除一个 session,它内部包含一个错误信息的回调函数:

exports.postLogout = (req, res, next) => {
  req.session.destroy((err) => {
    console.log(err);
  });
};

注意:该删除会将数据库中的 session 删除,浏览器里的 cookie 不会删除,但是其中 cookie 存储的 session 的 ID 在删除后会在数据库中找不到匹配。

确保 session 保存/更新后处理逻辑

有时候我们执行代码还没有等到 session 保存/更新好之后就已经执行完了,如果想等 session 完全保存好之后再进行执行,可以使用req.session.save()

req.session.save((err) => {
  console.log(err);
  res.redirect("/");
});

小结

Cookie:

  • 存储在浏览器上(客户端);

  • 可以被用户看到和操纵,不应该用来存储一些敏感的信息;

  • 通常不会直接设置 cookie 的值,而是通过类似 session 的包使用 cookie;

  • 可以配置各种各样的 cookie,如什么时候失效(expire);

    默认情况下在浏览器关闭时过期,而这类 cookie 被称为session cookie

    • session cookie不一定用来识别 session,之所以被称为session cookie是因为它们只在使用当前浏览器页面时存在;
  • 设置 cookie 的失效时间到具体某个日期、年龄,这相当于某种意义上的永久 cookie(它不会在关闭浏览器时就消失);

  • cookie 能和 session 一起使用,但它并不仅限于和 session 一起使用

Session:

  • 存储在服务器上(服务端);
  • 用户无法查看或操纵,适合存储跨域请求保留的敏感数据;
  • 可以在 session存储任何形式的数据,通常用来存储用户数据或身份验证状态信息;
  • seesion 需要一个 cookie 来识别(这个 cookie 不是session cookie,而是永久 cookie),一个 cookie 识别一个 session;
  • 可以选择不同的方式存储 session 数据,如:数据库存储、文件存储等;