会话令牌 vs JWT:错误的二分法

发布: (2026年1月2日 GMT+8 18:56)
6 min read
原文: Dev.to

Source: Dev.to

(请提供您希望翻译的正文内容,我将为您翻译成简体中文,并保留原有的格式、Markdown 语法以及技术术语。)

“仅 JWT” 方法

// User logs in
const jwt = sign(
  { userId: user.id, role: user.role },
  SECRET,
  { expiresIn: '7d' }
);
setCookie('token', jwt);

// Every request
const payload = verify(req.cookies.token, SECRET);
// Done. No database hit.

优点

  • 无需数据库查询——每台服务器都可以验证令牌。
  • “无状态”认证(不管这到底意味着什么)。

缺点

  • 撤销问题——如果用户被删除或解雇,其 JWT 在最长 7 天内仍然有效。
  • 角色数据陈旧——更改用户角色不会影响已签发的令牌。
  • 没有“全局登出”——被盗的笔记本、遗失的设备等会在令牌过期前保持有效。

结果: 速度快(≈0.97 ms/请求)且吞吐量高(≈5,527 请求/秒),但无法撤销令牌,数据会立即变得陈旧。

“仅会话” 方法

// User logs in
const sessionId = randomBytes(32).toString('hex');
await db.session.create({
  id: sessionId,
  userId: user.id,
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
setCookie('sessionId', sessionId);

// Every request – database lookup
const session = await db.session.findUnique({
  where: { id: req.cookies.sessionId },
  include: { user: true }
});

if (!session || session.expiresAt /* … */) {
  // handle missing or expired session
}

Result: 安全但慢;数据库成为瓶颈。

为什么混合方法有效

混合方法仅在 访问令牌过期 时触及存储(例如,大约 1 % 的请求)。对于其余 99 % 的请求,你可以获得 JWT 级别的速度,而无需网络调用。

关于 Redis 的简要说明

如果将 PostgreSQL 替换为 Redis 进行会话存储,查询时间会降至约 2–3 ms,但仍然会在 每一次 请求上进行外部调用。混合方法可以消除绝大多数流量中的这一次调用。

“微服务” 反驳

“微服务不共享数据库,所以 JWT 是唯一可以独立验证令牌的方式。”

实际上,微服务已经共享:

  • 用于持久化的数据库(或集群)
  • 用于共享状态的 Redis(或其他缓存)
  • 消息队列、日志、监控等

如果你已经有 Redis 用于缓存,会话验证成本很低。混合方式仍然通过避免 99 % 请求的网络往返而获胜。

解决方案:短期访问令牌 + 长期刷新令牌

不是 JWT 与会话的对立,而是 JWT + 会话,各自发挥最擅长的功能。

工作原理

// User logs in
async function login(email, password) {
  const user = await authenticateUser(email, password);

  // Refresh token – stored in DB (valid for 30 days)
  const refreshToken = randomBytes(32).toString('hex');
  await db.session.create({
    userId: user.id,
    refreshToken,
    expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
  });

  // Access token – NOT stored, just a JWT (valid for 15 minutes)
  const accessToken = jwt.sign(
    { userId: user.id, role: user.role },
    SECRET,
    { expiresIn: '15m' }
  );

  // Send tokens to the client
  res.cookie('accessToken', accessToken, { httpOnly: true });
  res.cookie('refreshToken', refreshToken, { httpOnly: true });
}
  • 访问令牌(JWT) – 短期有效,本地验证,无需 DB/Redis 查询。
  • 刷新令牌(会话) – 长期有效,存储在 DB/Redis 中,仅在访问令牌过期或用户主动登出时使用。

刷新流程(简化版)

// Refresh endpoint
async function refresh(req, res) {
  const { refreshToken } = req.cookies;
  const session = await db.session.findUnique({
    where: { refreshToken },
    include: { user: true }
  });

  if (!session || session.expiresAt /* … */) {
    // handle missing or expired refresh token
    return;
  }

  // Issue a new short‑lived access token
  const newAccessToken = jwt.sign(
    { userId: session.user.id, role: session.user.role },
    SECRET,
    { expiresIn: '15m' }
  );

  res.cookie('accessToken', newAccessToken, { httpOnly: true });
}

要点: 仅使用会话会把认证系统变成数据库瓶颈。混合方案在保留 JWT 高速验证的同时,加入了会话式的控制能力。

完整的基准测试结果请参见 stat-tests/RESULTS.md,测试代码位于 stat-tests/test-three-auth-strategies

建议

  • 使用混合方案 适用于几乎所有情况。它能提供 JWT 级别的速度和会话级别的安全性。
  • 仅使用 JWT 仅在令牌极短生命周期(< 5 分钟)且你真的不在乎撤销(极少情况)时使用。
  • 仅使用会话 仅在小型应用(< 10 请求/秒)且简洁性胜过性能考虑时使用。

你不必非此即彼。既享受 JWT 的速度 获得会话的控制——只需权衡利弊,而不是盲目崇拜单一方案。

祝玩得开心!

Back to Blog

相关文章

阅读更多 »

Academic Suite 身份验证与授权

3.1 Academic Suite 中的身份验证方法 Academic Suite 使用基于 JSON Web Token(JWT)的无状态身份验证方法。不同于基于会话的身份验证…