HTTP 无状态

# HTTP 无状态

关闭网页,再次访问服务器,服务器是没有意识到又是你来访问的,那么如何保持登陆的状态呢?

浏览器需要想办法帮我们在每一次请求里加入用户名和密码。

把用户名和密码放在 Cookie 中是很不安全的

# Session

# 描述

不同的网站对每个用户的会话都设定了时间(结束会话的时间,因为可能用户还没想退出,只是点错了而已)以及唯一的 ID(Session ID)

因为是服务器设定的,所以一般会保存在数据库中

  1. 第一次使用用户名密码发送给服务器,服务器核对身份,核对成功后在服务器端创建一个 Session ID 和会话结束时间(还会有其他参数);

  2. 服务器利用 Set-Cookie 像浏览器发送 Session ID 和会话结束时间,并把 Session ID 和过期时间加入 Cookie 当中;

  3. 浏览器保存 Cookie(没有保存用户名和密码),浏览器之后的访问都会自动发送这个 Session ID 给服务器,知道 Cookie 的有效期失效之后(浏览器会自行删除);

  4. Cookie 失效之后用户就得重新输入用户名和密码;

# 案例

const express = require("express");
const sessions = require("express-session");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extend: true }));

const usename_DB = "sgy";
const password_DB = "12345";
var session_DB;

app.use(
  sessions({
    sercet: "sgyissomething",
    name: "connect sid",
    cookie: { maxAge: 15000 },
    resave: false,
    saveUninitialized: false,
  })
);

app.get("/", (req, res) => {
  console.log("用户访问了界面");
  // 保存请求体里面的session
  session_DB = req.session;
  // 如果用户已经登陆过了
  if (session_DB.username) {
    res.send(
      `
      <h1>欢迎回来,${session_DB.username}</h1>
      <a href="/logout">logout</a>
      `
    );
  } else {
    res.sendFile(__dirname + "/index.html");
  }
});

app.post("/login", (req, res) => {
  if (req.body.username === username_DB && req.body.password === password_DB) {
    // 如果用户名和密码匹配,就生成新的session
    // 并把给session添加一个新的属性username
    session_DB = req.session;
    session_DB.username = req.body.username;
    console.log("服务器生成新的session对象");
    console.log(res.session);
    console.log(`当前的唯一会话ID,藏在cookie里得value:${req.sessionID}`);
    res.send(
      `
      <h1>欢迎回来,${session_DB.username}</h1>
      <a href="/logout">logout</a>
      `
    );
  } else {
    res.send(`用户名或者密码错误`);
  }
});

app.get("/logout", (req, res) => {
  // 登出清除session
  req.session.destroy();
  console.log(`用户退出网页后,当前服务器的session内容为:${req.session}`);
  res.redirect("/");
});

app.listen("3838", () => console.log("3838 running!"));

# 发展

随着用户群体变得越来越大,如果服务器依旧使用基于 Cookie 的 Session,在特定时间有大量用户访问服务器的时候,服务器就会面临可能需要存储大量 Session ID 在服务器里。

如果有多台服务器,一台服务器存储了 Session ID 又会面临需要分享 Session ID 给其他服务器,因为可能面临服务器的超载,需要分配一些用户到其他服务器,其他服务器需要通用的 Session ID 才可以避免用户再次输入用户名和密码,但是服务器来回分享也不是办法,如是让数据库存储 Session ID,但是数据库也面临着崩溃的风险。

随后出现了一种技术——JWT(Json Web Token)

# JWT

# 描述

  1. 用户第一次登录之后,服务器就会生成一个 JWT,服务器不需要保存这个 JWT,只需要保存 JWT 签名的密文,接着把 JWT 发送给浏览器

  2. 浏览器可以以 Cookie 或者 Storage 的形式进行存储

  3. 后续每次请求都会把这个 JWT 发送给服务器,这样就不用重复输入用户名和密码了,只不过 Token 存储在客户端而已

# 构成

header.payload.signature // 算法.数据.签名

  1. header 部分声明需要用什么算法来生成签名

  2. payload 部分是一些特定的数据,比如有效期之类

  3. header + payload 内容会由 Base64 进行编码(不是加密,也就是可以很容易解码)

  4. 服务器保存的密码配合编码之后的 header + payload 进行算法运算,最终得到签名信息

# 案例

const express = require("express");
const sessions = require("jsonwebtoken");
const { decode } = require("punycode");
const app = express();

const usename_DB = "sgy";
const password_DB = "12345";
const jwtSecret = "服务器JWT密码";

app.use(express.json());

app.post("/login", (req, res) => {
  if (req.body.username === usename_DB && req.body.password === password_DB) {
    const token = jwt.sign({ name: "sgy" }, jwtSecret, {
      algorithm: "HS256",
      expiresIn: 10000,
    });
    res.send(token);
  } else {
    res.send("账号密码错误");
  }
});

app.post("/vip", (req, res) => {
  jwt.verify(req.body.token, jwtSecret, (err, decode) => {
    if (err) {
      res.send("账号或密码错误");
    } else {
      res.send(`欢迎${decoded.name}用户`);
    }
  });
});

app.listen(5000, () => console.log("5000 Running..."));

# 总结

Session 是诞生并且保存在服务器那边的,由服务器主导一切,而 Cookie 则是一种数据载体,把 Session 放入 Cookie 中送到客户端那边,Cookie 跟随着 HTTP 的每个请求发送出去;

Token 是诞生在服务器,但保存在浏览器这边,由客户端主导一切,可以放在 Cookie 或者 Storage 里面,持有 Token 就像持有令牌一样可以允许访问服务器。