JsonMind
安全与认证9 分钟阅读更新于 2026年11月9日

JWT 和 Session 怎么选:撤销、扩展与安全边界

比较无状态 JWT 与服务端 Session 在撤销、扩展、XSS、CSRF 和移动端场景下的差异,给出可落地的选型建议。

JM作者:JsonMind Engineering最后复核: 2026年11月9日依据权威规范与引用来源复核。
快速答案

需要即时撤销、以浏览器为主的应用优先用服务端 Session;需要跨服务无状态校验或支持移动端时,用短期 JWT 加 refresh token。两者本身没有绝对的安全高低,风险取决于存储位置、传输方式、撤销机制和有效期。

核心要点
  • Session 把状态放在服务端,JWT 把签名后的声明放在客户端。
  • Session 可以即时撤销;JWT 需要短期过期、轮换或撤销列表。
  • 把 JWT 放进 localStorage 会暴露给 XSS;浏览器场景优先 HttpOnly Cookie。
  • Cookie 认证需要 CSRF 防护;Authorization 头不自动发送,但主要风险变成 XSS。
  • 短期 access token 加可撤销 refresh token 是常见的生产折中。

认证方案的讨论经常变成格式之争:JWT 还是 Session?更有用的问题是:状态放在哪里、如何撤销、凭证泄露后会发生什么。

这篇文章按生产系统真正关心的维度比较两种模型。

格式之争容易掩盖真正决定设计的问题:状态放在哪里、凭证泄露后多快失效、谁能读到声明、签名密钥泄露时影响多大。先把这几个问题回答清楚,Token 格式通常自然就定了。

服务端 Session 怎么工作

  1. 用户提交账号密码。
  2. 服务端创建会话记录和不透明 ID。
  3. 浏览器通过 HttpOnly Cookie 保存 ID。
  4. 每次请求,服务端查出会话并挂载用户。

客户端看不到会话内容。服务端删除记录就能让会话失效。代价是需要会话存储,并且每次请求都要查询。

会话 ID 必须不透明且不可预测:至少 128 位、来自加密安全随机源,不能是用户名、时间戳或自增序号。存储通常是 Redis 或数据库,查询会多一次网络往返,这是换取即时撤销和小体积 Cookie 的代价。会话记录应包含过期时间、用户 ID 和足够的元数据,用来支持“退出所有设备”。

Cookie 属性在这里很关键:HttpOnly 阻止脚本读取,Secure 防止明文传输,SameSite=Lax 挡住大多数跨站发送,Path 限制作用范围。登录成功后要重新生成会话 ID,防止会话固定;退出时既要清 Cookie,也要删除服务端记录。只清浏览器 Cookie、服务端会话仍然有效,是很常见的审计问题。

JWT 怎么工作

  1. 用户提交账号密码。
  2. 服务端签发包含 sub、role、exp 等声明的 Token。
  3. 客户端保存 Token,并在每次请求时发送。
  4. 服务端验签并读取声明,不需要查询会话。

Token 自带信息。代价是撤销困难:只要签名有效且未过期,Token 就一直可用,除非服务端额外检查。

JWT 由三段 Base64URL 组成:头部如 {"alg":"RS256","kid":"2026-11"},载荷如 {"sub":"user_123","role":"admin","iss":"https://auth.example.com","aud":"api","exp":1762900000},以及覆盖前两段的签名。Base64URL 是编码,不是加密:拿到 Token 的人都能读声明,所以不要把密钥或敏感个人信息放进去。签名只有在验证方按白名单检查算法、按 kid 找到正确公钥,并校验 iss、aud、exp、nbf 之后,才能证明完整性和来源。漏掉这些检查,有效签名会带来虚假的安全感。

对比

维度服务端 SessionJWT
状态位置服务端客户端
撤销即时需要额外状态
扩展共享存储或粘性会话无状态校验
浏览器存储HttpOnly CookieCookie 或 localStorage
XSS 暴露会话 ID 不可被 JS 读取localStorage 可被 JS 读取
CSRF 暴露Cookie 场景存在仅 Cookie 场景存在
移动端Cookie 容器或自定义头Bearer 头
微服务共享会话存储本地验签
Token 体积小包含声明,较大
退出服务端删除记录,立即失效客户端删除副本,Token 仍然有效
密钥轮换不涉及签名密钥需要 JWKS 重叠和 kid 切换

有两行需要展开。第一,“退出”含义不同:删除会话记录立即终止访问;删除客户端上的 JWT,只是删除本地副本,复制过 Token 的人仍然能用。第二,Session 没有签名密钥,轮换几乎无成本;JWT 验签则依赖在旧 Token 过期前把新公钥分发出去。

撤销是核心决策

Session 删除记录即可撤销。JWT 要撤销,必须增加一种机制:

  • 短期过期:接受被盗 Token 在过期前仍可用。
  • Refresh token 轮换:refresh token 存在服务端,可撤销;access token 保持短期。
  • Token version:在声明里带版本号,每次请求和用户记录比较。
  • 撤销列表:保存已撤销的 Token ID,直到过期。

每种方案都会增加状态。如果每个请求都要求即时撤销,本质上是在用 JWT 重新实现 Session。

撤销不只针对退出登录。管理员被降级后,旧 Token 里的角色声明会一直有效到过期;重置密码不会让已签发的 Token 失效;笔记本丢失后,攻击者能用满整个有效期;员工离职后可能还有几小时访问权限。选 Token 有效期之前,先为每种事件写下可接受的最大撤销窗口。

最可靠的 JWT 撤销方案都在向 Session 借状态。Refresh token 轮换在服务端保存 token family;旧 refresh token 被重复使用时,服务端撤销整个 family 并强制重新登录。Token version 声明和用户记录比对,可以在改密码时一次性废掉所有 Token。用 jti 维护撤销列表也能工作,但列表会随签发量增长,还会重新引入你本来想避免的查询。按可接受的撤销窗口选机制,并把窗口写进文档。

扩展与微服务

多个服务需要本地校验身份、又不想共享 Session 存储时,JWT 很有吸引力。

代价是:

  • 密钥分发和轮换变得关键。
  • 签名密钥泄露会影响所有服务。
  • 必须用算法白名单防止算法混淆。
  • 声明在签发和过期之间可能变旧。

单一后端能维护状态时,Session 更简单。扩展需要 Redis 之类的共享存储,或者粘性会话。粘性会话会降低灵活性,也让发布更复杂。

JWT 的密钥管理有自己的故障模式:算法混淆。如果验证方接受 Token 头部里写的算法,攻击者可以诱导它接受 alg: none,或者用公钥当 HMAC 密钥去验证 HS256 签名。必须白名单限定允许的算法,绝不让头部决定验证方式;kid 查不到对应密钥时直接拒绝,不要回退到默认密钥。

密钥轮换需要重叠窗口。在启用新密钥签发之前,先把新公钥发布到 JWKS 端点;旧密钥要保留到最长有效期的 Token 全部过期;切换期间监控验签失败。签名密钥泄露比会话存储泄露更严重的一点是:所有信任该密钥的服务都会受影响,轮换会一次性作废所有未过期 Token。

浏览器存储:XSS 与 CSRF

JWT 放在 localStorage 时,页面上的任何 JavaScript 都能读取。一个 XSS 漏洞就可能泄露所有 Token。HttpOnly Cookie 不能被 JavaScript 读取,提高了窃取成本。

Cookie 会带来 CSRF 风险,因为浏览器自动发送。缓解手段包括 SameSite=Lax 或 Strict、CSRF Token 和来源校验。放在 Authorization 头里的 Token 不会自动发送,所以 CSRF 不是主要风险,XSS 才是。

没有一种存储方式绝对安全。选择取决于威胁模型,以及前端和 API 是否同站。

一个具体的 XSS 故障:评论框把用户输入当 HTML 渲染,攻击者注入脚本,脚本读取 localStorage.getItem("token") 并发到收集端点,攻击者拿到 Token 后可以用到过期。HttpOnly 去掉了这条读取路径,但没有消除 XSS:注入的脚本仍然能在页面里发起已认证请求,所以输入清洗和 CSP 仍然必要。

Cookie 认证不能只靠 SameSite。默认用 SameSite=Lax,高风险操作用 Strict,只有确实需要跨站流程时才用 None; Secure。对会改变状态的请求,再加一个和会话绑定的 CSRF Token,检查 Origin 和 Referer,并且不要用 GET 执行写操作。单页应用可以采用 BFF:Token 只存在服务端,浏览器只拿 HttpOnly 会话 Cookie,JavaScript 完全接触不到 Token。

移动端和第三方客户端

移动 App 和 CLI 没有浏览器的 Cookie 模型,Bearer Token 更自然。同一个后端同时服务浏览器和移动端时,常见做法是混合:

  • 浏览器:HttpOnly 会话 Cookie,或短期 access token Cookie。
  • 移动端:短期 access token 加 refresh token。
  • 两端:服务端保存 refresh token 记录,用于撤销。

移动端还有自己的泄露路径。Token 被写进普通文件、日志、崩溃报告或备份,都可能从丢失的设备里被提取。应存进系统钥匙串或 Keystore,access token 尽量只放内存,refresh token 绑定设备标识,以便发现被复制的 Token。证书固定能提高中间人攻击成本,但会让轮换变复杂;没有更新固定证书的方案就不要启用。

实用的混合方案

很多生产系统使用:

  1. 5 到 15 分钟的短期 access token。
  2. refresh token 放在 HttpOnly Cookie 或安全存储。
  3. 服务端保存 refresh token 记录,可撤销。
  4. 每次刷新都轮换,并检测重复使用。
  5. 校验 exp 时允许小的时钟容差。

这样大多数请求是无状态的,同时保留撤销路径。复杂度集中在刷新流程,而不是 access token。

最先撞上的竞态是并发刷新。两个标签页或两个接口调用同时刷新;如果轮换在第一次使用时就让旧 Token 失效,第二个请求会失败,甚至触发退出登录。解决办法包括:给旧 Token 一个很短的宽限窗口、每个会话加刷新锁,或者服务端对几秒内的重复请求返回同一个新 Token。重复使用检测必须区分这种良性竞态和真正被盗的 Token,否则用户正常浏览时也会被登出。

客户端能支持时,access token 应该只放内存。为了省事持久化到磁盘,会重新引入短期过期本来要缩小的窃取窗口。refresh token 才是长期凭证,值得最强的存储保护和最细的监控:换国家刷新、换设备刷新、改密后刷新,都应该告警。

选型建议

选择 服务端 Session:

  • 需要即时退出和撤销。
  • 应用以浏览器为主,由单一后端提供服务。
  • 想要最简单的心智模型。
  • 能运维会话存储。

选择 JWT 加 refresh token:

  • 多个服务需要本地校验身份。
  • 移动端或第三方客户端需要 Bearer Token。
  • 能管理密钥轮换和短期过期。
  • 能接受短暂的撤销窗口。

避免:

  • 长期有效、没有撤销路径的 JWT。
  • 把 JWT 放进 localStorage,却没有严格 CSP 和短期过期。
  • 使用 Session Cookie 却没有 CSRF 防护。
  • 验签之前就信任 JWT 声明。
需求更合适的选择原因
退出必须立即生效Session服务端删除记录
上百个服务本地校验身份JWT本地验签
单一后端的浏览器应用Session攻击面更小
移动端和 CLIJWT 加 refreshBearer 头更适合客户端
强多租户权限Session 或混合每次请求拿到最新角色
离线验签JWT不查询存储

这个建议也会因为动机错误而失效。只因为 JWT 听起来现代就选它,却没有密钥轮换和撤销方案,会同时得到两种模型的缺点。只因为 Session 简单就选它,却又要给移动端提供公开 API,会把 Cookie 处理硬塞进原生应用。反过来,单一后端加 Redis 会话存储,往往比一整套 JWT 加 refresh token 平台更便宜;一个永远不会有第二个服务的系统,不值得引入分布式签名复杂度。

调试 Token

用 JWT 解析工具 查看声明,用 JWT 签发工具 生成测试 Token。密码哈希生成工具 可以测试凭据存储,Basic 认证头生成工具 适合旧接口。更完整的过期排查可以参考 JWT 过期指南。

Token 问题的检查清单:解码三段并确认载荷符合预期;用秒而不是毫秒比较 exp 和 nbf 与当前时间;确认 aud 是本服务、iss 是预期签发方;确认 kid 在密钥集合里存在;分别测试过期 Token、用错算法签发的 Token、被改过载荷的 Token。密钥正确却验签失败,通常是签名字节不一致,例如代理改写了空白,或者 Token 被 URL 解码了两次。

认证是一个系统:凭据存储、传输、会话生命周期、撤销、监控和用户体验。Token 格式只是系统中的一个选择,不是系统本身。

常见问题

JWT 比 Session 更安全吗?

不能一概而论。JWT 存在 localStorage 时可能被 XSS 窃取,长期有效且无法撤销的 JWT 风险更大。Session Cookie 也可能被窃取,但服务端可以立即让会话失效。安全性取决于实现,而不是格式本身。

JWT 可以撤销吗?

需要额外状态:短期过期、refresh token 轮换、撤销列表,或者用户记录上的 token version。完全无状态的 JWT 在过期前一直有效。

浏览器里 JWT 应该存在哪里?

前后端同站时,优先使用 HttpOnly、Secure、SameSite 的 Cookie。localStorage 可以被页面上的任何脚本读取,一个 XSS 漏洞就可能导致 Token 被窃取。如果必须用 localStorage,应缩短有效期并配置严格 CSP。

用 JWT 还需要 CSRF 防护吗?

如果 JWT 存在 Cookie 里并由浏览器自动发送,需要。CSRF 利用的就是自动携带 Cookie。如果 Token 由 JavaScript 放在 Authorization 头里发送,CSRF 不是主要风险,XSS 才是。

微服务应该用 JWT 还是 Session?

多个服务需要本地校验身份、又不想共享 Session 存储时,JWT 更方便。单一后端能维护会话状态时,Session 更简单。核心取舍是撤销能力和密钥管理。

参考资料

把指南用起来

浏览全部工具

打开浏览器工具。输入内容保留在浏览器中,不会上传到业务服务器。

相关指南

全部指南