Cookie、Session、Token

Cookie、Session、Token 与 JWT 在 Web 鉴权和状态保存中的基本分工,以及它们在浏览器、服务端与分布式系统中的取舍边界。

#tech / security #type / concept #status / growing #resource / http

[!info] related notes

Cookie、Session、Token

Cookie、Session、Token 经常被放在一起讨论,但它们并不是同一层概念。

一句话定义

  • Cookie 是浏览器保存并可能自动携带数据的机制
  • Session 是服务端保存会话状态的方案
  • Token 是客户端持有并主动携带的认证凭证

和 JWT 的关系

  • JWT 是 Token 的一种常见格式,不是独立于 Token 的新方案
  • JWT 可以放在 Cookie 里,也可以放在请求头里
  • 真正要比较的是“状态放服务端还是客户端”“凭证怎么传”“怎么撤销”

为什么这三个概念总被混在一起

因为它们共同回答的是一个 Web 系统的核心问题:

HTTP 本身无状态,但业务系统需要知道“你是谁、你现在是不是已登录、这次请求能不能代表你”。

这三个概念分别落在不同层:

  • Cookie:浏览器端的携带机制
  • Session:服务端的状态保存方案
  • Token:客户端持有的认证凭证

所以最稳的分层说法是:

Cookie 是载体,Session / Token 是会话与鉴权方案,JWT 是 Token 的一种格式。

HTTP 协议是无状态的,不保存任何信息。但是鉴权、获取对应用户数据等显然需要知道用户是谁等信息的。
Cookie 就是最开始使用的用于保存信息的技术。

Cookie 是由服务器发送到用户浏览器并保存在本地的一小块数据,会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

特性

  • 保存的是一段文本信息
  • 大小受限,小于 4KB
  • 由服务器设置,客户端存储
  • 自动随 HTTP 请求发送
  • 有域名和路径限制
  • 支持过期时间设置
属性说明示例
Name=ValueCookie 的名称和值username=john
Domain指定 Cookie 的域名Domain=example.com
Path指定 Cookie 的路径Path=/admin
Expires/Max-Age过期时间Max-Age=3600
HttpOnly禁止 JavaScript 读取,降低 XSS 窃取风险HttpOnly
Secure只在 HTTPS 传输Secure
SameSite限制跨站自动携带,降低 CSRF 风险SameSite=Strict

两个最容易误解的属性是:

  • HttpOnly Cookie:浏览器仍会自动带上,但 JavaScript 不能通过 document.cookie 读到
  • SameSite:控制跨站场景下浏览器是否自动附带 Cookie

使用示例

实现代码
// 设置 Cookie
document.cookie = "theme=dark; Max-Age=3600; Path=/; Secure";

// 读取 Cookie
function getCookie(name) {
  const cookies = document.cookie.split(";");
  for (let cookie of cookies) {
    const [key, value] = cookie.trim().split("=");
    if (key === name) return value;
  }
  return null;
}

// 删除 Cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";

注意:

  • document.cookie 只能设置普通 Cookie 属性,不能把 Cookie 设置成 HttpOnly
  • HttpOnly 只能由服务端通过 Set-Cookie 下发

服务器端设置

# HTTP 响应头
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
Set-Cookie: theme=dark; Max-Age=2592000; Path=/

Session

Session 是一种服务器端的数据存储技术,用于存储用户会话信息。Session 数据存储在服务器端,通过 Session ID 来标识不同的会话。

工作机制

  1. 用户首次访问,服务器创建 Session 并生成唯一 Session ID
  2. Session ID 通过 Cookie 发送给客户端
  3. 客户端后续请求携带 Session ID
  4. 服务器根据 Session ID 查找对应的 Session 数据

Session 生命周期

graph LR
    A[用户登录] --> B[服务器创建 Session]
    B --> C[返回 Session ID]
    C --> D[客户端存储 Session ID]
    D --> E[后续请求携带 Session ID]
    E --> F[服务器验证并获取 Session 数据]
    F --> G[Session 过期或用户登出]
    G --> H[销毁 Session]

Session 的工程特点

  • 服务端可控,适合做强会话管理
  • 可以主动失效、强制下线、实时收回权限
  • 分布式部署通常要把 Session 放进 Redis 等共享存储

所以 Session 的核心 trade-off 是:

  • 控制力强
  • 但有服务端状态成本

服务器端实现(Node.js Express)

实现代码
const session = require("express-session");

app.use(
  session({
    secret: "your-secret-key",
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: false, // HTTPS 环境设为 true
      httpOnly: true,
      maxAge: 1000 * 60 * 30, // 30 分钟
    },
  })
);

// 设置 Session
app.post("/login", (req, res) => {
  req.session.userId = user.id;
  req.session.username = user.username;
  res.json({ success: true });
});

// 获取 Session
app.get("/profile", (req, res) => {
  if (req.session.userId) {
    res.json({ userId: req.session.userId });
  } else {
    res.status(401).json({ error: "Not authenticated" });
  }
});

// 销毁 Session
app.post("/logout", (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      res.status(500).json({ error: "Could not log out" });
    } else {
      res.json({ success: true });
    }
  });
});

Token

Token 是一种无状态的认证机制,通常是一个加密的字符串,包含了用户信息和过期时间等数据。

加密后在客户端存储: 服务端就通过定义的解密方式,比如 jwt 来对 token 进行解码,只要正确就放行 服务端没有方法禁用一个Token,除非加入黑名单。 所以在 双 token 机制中,需要让 refresh 为其他加密方法,需要从数据库重新获取对比,确保随时能撤回

Token 的工程特点

  • 更适合分布式 API
  • 服务端可以减少会话查表
  • 更容易跨服务透传身份
  • 但“主动失效”比 Session 更麻烦

因此面试里更稳的说法通常是:

  • 普通后台系统:Session 依然很合理
  • SPA / 移动端 / 微服务 API:常见 Access Token + Refresh Token

JWT (JSON Web Token)

JWT 是目前最流行的 Token 实现方式,由三部分组成:

  1. Header:包含算法和令牌类型
  2. Payload:包含声明(用户信息)
  3. Signature:用于验证令牌的签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Token 使用流程

sequenceDiagram
    participant C as 客户端
    participant S as 服务器

    C->>S: 1. 登录请求 (用户名/密码)
    S->>S: 2. 验证用户信息
    S->>C: 3. 返回 JWT Token
    C->>C: 4. 存储 Token
    C->>S: 5. 请求时携带 Token (Authorization Header)
    S->>S: 6. 验证 Token
    S->>C: 7. 返回数据

最关键的取舍应该怎么讲

Session 更适合什么时候

  • 想要强控制会话
  • 需要主动踢下线
  • 权限变化要尽快生效
  • 传统 Web 系统、后台系统较多

Token / JWT 更适合什么时候

  • 想做无状态 API
  • 多端接入
  • 微服务间传递身份
  • 前后端分离或移动端场景

最容易误解的点

  • Cookie 和 Token 不是对立关系,Token 也可以放在 Cookie
  • JWT 不是“更安全的 Session 替代品”,它只是 Token 格式
  • 安全重点不只是“放哪”,还包括过期、刷新、撤销、XSS、CSRF 与服务端约束

实现示例

服务器端生成和验证 Token

code
const jwt = require("jsonwebtoken");
const SECRET_KEY = "your-secret-key";

// 生成 Token
function generateToken(user) {
  const payload = {
    userId: user.id,
    username: user.username,
    exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1小时过期
  };
  return jwt.sign(payload, SECRET_KEY);
}

// 验证 Token 中间件
function verifyToken(req, res, next) {
  const token = req.headers["authorization"]?.split(" ")[1]; // Bearer token

  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// 登录接口
app.post("/login", async (req, res) => {
  // 验证用户信息...
  const token = generateToken(user);
  res.json({ token, user: { id: user.id, username: user.username } });
});

// 受保护的路由
app.get("/protected", verifyToken, (req, res) => {
  res.json({ message: "This is protected data", user: req.user });
});

客户端使用 Token

code
// 登录并获取 Token
async function login(username, password) {
  const response = await fetch("/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
  });

  const data = await response.json();
  if (data.token) {
    localStorage.setItem("token", data.token);
  }
  return data;
}

// 携带 Token 的请求
async function fetchProtectedData() {
  const token = localStorage.getItem("token");

  const response = await fetch("/protected", {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return response.json();
}

// 登出(清除 Token)
function logout() {
  localStorage.removeItem("token");
}

对比

对比项CookieSessionToken
存储位置客户端(浏览器)服务器端客户端(通常在 localStorage/sessionStorage)
存储大小4KB 限制服务器内存/磁盘限制无严格限制
安全性较低,可被篡改较高,服务器端控制中等,可加密但在客户端
跨域支持受同源策略限制受同源策略限制支持跨域
服务器负载有(存储会话数据)无(无状态)
扩展性差(服务器集群需要会话同步)好(无状态)
过期控制支持支持支持

面试要点

来自 cookie-session-token-jwt-interview-question 的面试视角整理。

一句话回答

Cookie 是浏览器保存和自动携带数据的机制;Session 是服务端保存会话状态的方案;Token 是客户端持有的认证凭证;JWT 是一种自包含结构化 Token。

最稳的分层说法

浏览器端保存小块数据,并在合适条件下自动随请求发送。

Session

服务端保存用户会话信息,客户端通常只持有 Session ID。

Token

服务端签发一个认证凭证,客户端后续主动携带它完成身份验证。

JWT

JWT 是一种结构化 Token,通常由 Header、Payload、Signature 组成。

面试里最常见的比较点

  • 状态放在服务端还是客户端
  • 是否容易做分布式扩展
  • 是否自动携带
  • 安全风险和使用场景

常见误区

Token 也可以放在 Cookie 里,它们不是同一维度概念。

误以为 JWT 天然更安全

JWT 只是 Token 的一种格式,安全与否仍取决于存储方式、签名校验和整体鉴权设计。

最短记忆方式

Cookie 是载体,Session/Token 是方案,JWT 是 Token 的一种格式。

创建于 2025/1/1 更新于 2026/5/27