什么是 JWT?
JWT(JSON Web Token)是一种用于在网络各方之间传递声明的紧凑令牌格式。最常见的 JWT 是经过签名的 JWS:服务器把用户标识、权限范围和过期时间等 Claims 放进 Payload,再用密钥生成 Signature,客户端在后续请求中携带这串令牌,服务器验证后决定是否接受请求。

JWT 本质上是一串字符串,典型形态如下:
xxxxx.yyyyy.zzzzz
两个点把它分成三段:Header.Payload.Signature。前两段通常是 JSON 的 Base64URL 编码,第三段是对前两段内容计算出的签名。Base64URL 只是编码,不是加密,因此拿到 JWT 的人通常可以直接读出 Header 和 Payload。
JWT 主要解决的是“如何把可验证的信息随请求一起传递”。它可以减少服务器对集中式 Session 存储的依赖,适合 API、微服务和跨服务传递身份上下文。但 JWT 不是登录协议,也不是自动加密方案,更不是“只要生成了就安全”的万能 Token。
一句话总结:JWT 是可携带、可验证的声明容器;Signature 负责证明内容没有被改,HTTPS 和安全存储负责保护它在传输与使用过程中的机密性。
JWT 解决了什么问题?
JWT 的价值在于把部分认证上下文放进令牌本身。用户登录成功后,认证服务可以签发一个包含 sub、scope、exp 等声明的令牌;资源服务器收到令牌后,先验证签名、发行方和有效期,再根据声明执行授权判断。

例如,一个 API 令牌的 Payload 可能是:
{
"iss": "https://auth.example.com",
"sub": "user_12345",
"aud": "orders-api",
"scope": "orders:read",
"iat": 1787820000,
"exp": 1787820900,
"jti": "token-unique-id"
}
这些字段不是固定必须全部出现,但它们表达了常见语义:
iss(Issuer):谁签发了令牌。sub(Subject):令牌代表谁,通常是稳定的用户或服务主体 ID。aud(Audience):令牌准备交给哪个服务使用。scope:令牌被授予的操作范围。iat(Issued At):令牌何时签发。exp(Expiration Time):令牌何时失效。jti(JWT ID):令牌的唯一标识,可用于撤销或防重放记录。
需要注意的是,Payload 里的 role 或 scope 只是服务器签发的声明。客户端可以把它改成 admin,但没有合法签名时,资源服务器不应相信这个改动。真正的授权决定仍然由服务器完成,而不是由前端按钮是否显示来决定。
Session 为什么需要服务器保存状态?
传统 Session 认证把会话状态放在服务器或共享存储中。用户登录后,服务器生成一个随机的 Session ID,浏览器保存这个 ID;之后每次请求都带上 ID,服务器再根据它查找用户、登录状态和过期时间。

一个简化流程如下:
- 用户提交账号和密码。
- 服务器校验成功,在内存、数据库或 Redis 中创建会话记录。
- 服务器把随机 Session ID 通过 Cookie 返回给浏览器。
- 浏览器在后续请求中自动携带这个 Cookie。
- 服务器根据 Session ID 查询会话,确认身份后处理请求。
Session 的关键特点是:**Session ID 本身通常没有业务信息,服务器保存的会话记录才是权威状态。**因此,服务器可以删除这条记录来立即注销用户,也可以在后台修改角色后让下一次请求立刻使用新状态。
Session 的代价是需要管理状态。多台服务器部署时,会话要么固定路由到同一台机器,要么放入 Redis 等共享存储;存储容量、读写延迟、故障恢复和跨区域同步都需要额外设计。
JWT 如何把认证状态带到客户端?
JWT 的典型流程是“签发一次,验证多次”。服务器登录校验成功后生成 JWT,客户端保存它;后续请求把它放在 Authorization 请求头中,资源服务器本地验证后读取声明。

GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
一个完整流程可以拆成五步:
- 登录:客户端提交账号和密码,或通过 OAuth 2.0 / OpenID Connect 完成登录。
- 签发:认证服务校验身份,生成包含必要 Claims 的 JWT 并签名。
- 保存:客户端按应用架构保存 Access Token,必要时同时保存 Refresh Token。
- 携带:客户端请求 API 时使用
Authorization: Bearer <token>发送 Access Token。 - 验证:资源服务器检查签名、算法、发行方、受众、时间和权限,再处理业务请求。
第五步不能简化成“解码后读取用户 ID”。正确顺序是先验证令牌是否可信,再使用其中的声明。只解码不验签,相当于把用户提交的普通 JSON 当成服务器指令。
JWT 的三部分分别是什么?
JWT 由 Header、Payload 和 Signature 三部分组成,并用两个点连接。每一部分职责不同:Header 描述验证方式,Payload 携带声明,Signature 保护前两段的完整性。

Header.Payload.Signature
头部 载荷 签名
| 部分 | 典型内容 | 是否保密 | 主要职责 |
|---|---|---|---|
| Header | typ、alg、kid | 否 | 声明令牌类型、签名算法和密钥标识 |
| Payload | iss、sub、aud、scope、exp | 否(常见 JWS) | 携带可验证的身份和业务声明 |
| Signature | 算法计算出的字节串 | 不要求保密 | 检测前两段是否被篡改,并验证签发者掌握密钥 |
这里的“自包含”只表示令牌携带了验证所需的声明,不表示它具备机密性,也不表示服务器永远不需要查数据库。服务器仍可能查询用户是否被封禁、权限是否刚刚变化、令牌是否已撤销,或者订单等业务资源是否存在。
Header 做了什么?
Header 通常是一个 JSON 对象,常见字段如下:
{
"typ": "JWT",
"alg": "HS256",
"kid": "auth-key-2026-08"
}

typ表示令牌类型,通常写作JWT。alg表示签名算法,例如HS256或RS256。kid是可选的密钥 ID,帮助服务器在密钥轮换时选择正确的验证公钥或密钥。
服务器不能盲目信任客户端提供的 alg。验证端应为每种令牌类型配置允许的算法,并拒绝不在白名单中的算法。历史上曾出现过把算法改成 none,或混淆对称与非对称算法导致验证绕过的问题;现代库通常提供算法限制,但应用仍应显式配置。
Payload 为什么叫载荷?
Payload 是承载 Claims 的 JSON 对象。它可以放用户 ID、租户 ID、角色、权限范围和时间字段,但应遵循最小化原则:只放验证和授权真正需要的数据。

Payload 的内容可以被读取,所以不要放密码、银行卡号、身份证号、长期机密、内部密钥或不应暴露给客户端的业务数据。即便使用 HTTPS,也不能把“传输过程加密”误认为“令牌本身加密”:HTTPS 保护链路,JWT 的 Payload 默认仍可在客户端被解码。
严格来说,JWT 也可以使用 JWE(JSON Web Encryption)进行加密;但日常 API 里更常见的是签名的 JWS。本文讨论的 Header.Payload.Signature 三段形式就是常见的签名 JWT。需要机密性时,应评估 JWE 或直接把敏感数据放在服务器端,通过令牌只携带不敏感的引用。
Signature 如何防止 JWT 被篡改?
Signature 是由服务器使用指定算法和密钥,对编码后的 Header 与 Payload 计算得到的结果。以 HS256 为例,概念公式是:
signature = HMAC-SHA256(
base64url(header) + "." + base64url(payload),
shared_secret
)

服务器验证时,会对收到的前两段重新计算签名,再与第三段比较。只要 Header 或 Payload 被改动,结果就会不同;攻击者如果不知道正确密钥,也无法生成能通过校验的新签名。
HS256 使用同一个密钥签发和验证,适合由同一受信边界控制的服务。RS256、ES256 等非对称算法则使用私钥签发、公钥验证:认证服务保管私钥,多个资源服务器只需要拿到公钥,更适合服务拆分和跨团队验证。
签名证明的是“内容完整且由掌握密钥的一方签发”,不证明内容本身正确,也不阻止合法令牌被盗后重放。签名验证通过后,还必须检查 iss、aud、exp、nbf、权限范围和业务状态。
JWT 为什么适合无状态架构?
JWT 适合无状态架构,是因为资源服务器可以根据令牌本身完成基础验证,不必为每个请求查询一条 Session 记录。多个 API 实例只要共享验证配置或公钥,就能独立处理请求。

它带来的收益主要有:
- 横向扩展简单:请求可以被分发到任意 API 实例,不依赖固定的会话粘性。
- 跨服务传递清晰:服务可以根据
aud和scope判断令牌是否属于自己、允许做什么。 - 减少会话读取:基础身份验证不必每次查询 Redis 或数据库。
- 适合 API 客户端:移动端、前后端分离应用和第三方集成可以使用标准的 Bearer Token 方式。
但“无状态”是一个架构选择,不是性能保证。JWT 可能比一个随机 Session ID 更长,会增加请求头体积;令牌失效前无法天然感知服务端状态变化;服务器若要支持撤销、封禁和动态权限,又会重新引入黑名单或状态查询。
JWT 的验证流程应该检查什么?
资源服务器验证 JWT 时,至少要同时检查密码学有效性和业务语义。只检查签名,或者只检查 exp,都不足以判断一个令牌是否能访问当前资源。

建议按以下顺序设计验证逻辑:
- 提取令牌:读取
Authorization头,确认格式是Bearer <token>,拒绝缺失或格式异常的值。 - 限制算法:使用服务端配置的算法白名单,不让令牌自行决定可接受的算法。
- 选择密钥:根据可信来源和受控的
kid选择密钥,避免任意 URL 或任意密钥输入。 - 验证签名:确认令牌由受信任的签发方使用对应密钥签发,且内容未被篡改。
- 检查时间:验证
exp未过期,必要时检查nbf;服务器时钟应保持同步并设置合理时钟偏差。 - 检查来源和受众:验证
iss是预期的认证服务,aud包含当前 API,不能只看用户 ID。 - 执行授权:检查
scope、角色、租户和资源归属,确认这个主体有权执行当前动作。 - 检查业务状态:高风险操作可额外检查账号封禁、令牌撤销、设备状态和二次认证结果。
因此,认证与授权必须分开:**签名验证回答“这个令牌可信吗”,授权判断回答“它能不能做这件事”。**验证成功不等于可以访问所有接口。
JWT 和 Session 有什么区别?
JWT 与 Session 都可以用于身份认证,核心差别是“权威状态主要在哪里”。Session 把状态放在服务器;JWT 把可验证声明放进客户端携带的令牌,但服务器仍可以保留额外的撤销和业务状态。

| 对比维度 | JWT | Session |
|---|---|---|
| 状态位置 | 主要在客户端令牌中 | 主要在服务器或共享存储中 |
| 请求携带 | 常见是 Authorization: Bearer | 常见是 HttpOnly Cookie 中的 Session ID |
| 服务器查询 | 基础验证通常不需要查会话 | 通常需要根据 ID 查会话 |
| 横向扩展 | 依赖公钥或密钥配置,路由更灵活 | 需要共享存储或会话粘性 |
| 立即注销 | 需要黑名单、短过期或版本号等机制 | 删除服务端会话记录即可 |
| 权限变更 | 旧令牌中的声明可能仍有效 | 服务器状态可立即生效 |
| 泄露后果 | 持有者可在过期前作为 Bearer 使用 | 持有 Session ID 的人也可冒充用户 |
| 请求体积 | 可能较大,Claims 越多越长 | ID 通常较短 |
| 适合场景 | API、微服务、跨系统身份传递 | 传统服务端渲染 Web、需要即时撤销的会话 |
没有一种方案在所有场景都更好。单体 Web 应用使用带 HttpOnly、Secure、SameSite 属性的 Session Cookie,往往更容易实现注销和状态控制;多个资源服务器需要独立验证访问令牌时,JWT 可能更合适。架构决策应从撤销需求、信任边界、客户端类型和运维能力出发,而不是只看“无状态”三个字。
JWT 的安全边界在哪里?
JWT 的安全边界至少包括签名算法、传输通道、客户端存储、有效期、撤销机制和密钥管理。JWT 只解决“声明是否被篡改以及是否由可信方签发”的一部分问题,其他边界需要应用系统补齐。

1. 签名不等于加密
常见签名 JWT 的 Header 和 Payload 可被解码。签名保护完整性和来源,不保护机密性。不要把敏感信息放入 Payload;需要加密时使用 JWE,或只在 Token 中放一个不可推断的 ID,再从服务器端读取敏感数据。
2. Bearer Token 谁拿到谁能用
Authorization: Bearer 的含义是“持有令牌即可证明身份”,服务端通常无法仅凭令牌区分合法客户端和窃取者。因此,必须使用 HTTPS,避免把 Token 写入日志、错误追踪、分析平台、Referer 或 URL 查询参数。
3. localStorage 和 Cookie 没有绝对优胜者
localStorage 容易被前端 JavaScript 读取;一旦页面存在 XSS,攻击脚本可能直接窃取 Token。HttpOnly Cookie 可以阻止 JavaScript 读取 Cookie,但浏览器会自动携带它,因此需要防范 CSRF,并正确配置 SameSite、CSRF Token 和来源校验。
如果采用 Cookie,至少应考虑:
Secure:只通过 HTTPS 发送。HttpOnly:阻止普通 JavaScript 读取,降低 Token 被脚本直接窃取的风险。SameSite=Lax或更严格策略:减少跨站请求自动携带 Cookie 的机会。- CSRF 防护:对状态变更请求使用 CSRF Token 或严格的 Origin 校验。
如果采用浏览器内存保存 Access Token,应配合短过期时间和安全的刷新流程。无论选择哪种方式,XSS 都可能直接调用当前页面能调用的 API,所以内容安全策略、输入输出编码和依赖治理仍然重要。
4. 无法天然立即注销
JWT 一旦签发,通常会在 exp 到达前保持有效。用户点击“退出登录”时,客户端可以删除本地令牌,但已经被复制出去的令牌不会因此失效。
常见补救方式包括:
- Access Token 设置较短有效期,例如 15 分钟到数小时,并使用 Refresh Token 获取新令牌。
- 为高风险系统维护
jti黑名单,直到对应令牌自然过期。 - 在 Payload 中加入用户会话版本号,服务端只在必要场景查询当前版本。
- 对账号封禁、改密和权限升级等事件使用服务端状态检查。
黑名单会重新引入存储和查询成本,但这是“支持立即撤销”需要支付的代价。不要同时声称系统“完全无状态”和“任何时刻都能立即撤销所有 JWT”,除非明确说明使用了额外的服务端状态。
5. 密钥泄露会破坏信任根
签名密钥是 JWT 系统的信任根。HS256 的共享密钥一旦泄露,拿到密钥的服务就能签发任意 Token;RSA 或椭圆曲线方案的私钥泄露,也会让攻击者伪造令牌。因此密钥应放在 Secret Manager 或 KMS 中,限制读取权限,不要提交到代码仓库或写进前端。
生产系统还应支持密钥轮换:新令牌使用新密钥,验证端在过渡期保留旧公钥,并通过 kid 选择密钥;所有旧令牌过期后再移除旧密钥。密钥轮换无法撤回已经签发的令牌,所以仍需搭配短过期和撤销策略。

JWT 适合哪些场景?
JWT 更适合需要在多个请求或多个服务之间传递可验证身份上下文的场景,而不是所有登录系统都应该使用它。
适合 JWT 的场景包括:
- 前后端分离应用调用独立 API。
- 移动端或桌面客户端访问资源服务器。
- OAuth 2.0 访问令牌携带受众和权限范围。
- 微服务之间传递短期、明确受众的服务身份。
- 第三方登录或跨系统集成,需要验证签发方和受众。
Session 往往更适合以下场景:
- 传统服务端渲染 Web 应用。
- 需要管理员随时注销所有会话的后台系统。
- 权限变化频繁,且每次请求都必须获得最新状态。
- 团队更擅长维护成熟的 Cookie、CSRF 和 Session 基础设施。
无论选 JWT 还是 Session,都应通过 HTTPS 保护传输、设置明确的过期策略、记录安全事件,并对登录失败、刷新、注销和异常 Token 使用进行监控。
常见问题
JWT 是加密的吗?
通常不是。常见的三段式 JWT 是签名后的 JWS,Header 和 Payload 使用 Base64URL 编码,任何拿到令牌的人都可以解码查看。它能防止篡改,但不能隐藏内容;需要机密性时应使用 JWE 或服务端存储敏感数据。
JWT Payload 可以放密码吗?
不可以。Payload 默认可读,密码、银行卡号、私钥和其他敏感数据都不应放入其中。即使使用 JWE,也不应把 JWT 当作存放大量敏感业务数据的数据库。
JWT 和 OAuth 2.0 是一回事吗?
不是。JWT 是令牌格式,OAuth 2.0 是授权框架。OAuth 2.0 的 Access Token 可以采用 JWT,也可以是服务器端可查询的随机字符串;是否使用 JWT 取决于授权服务器与资源服务器的协议设计。
JWT 泄露后怎么办?
立即撤销相关 Refresh Token,必要时把 jti 加入黑名单、提升用户会话版本或轮换受影响的密钥;同时检查访问日志、缩短剩余令牌有效期,并要求用户重新登录。删除浏览器本地 Token 只能清理当前客户端,不能让已经复制出去的 Token 失效。
JWT 应该放在 localStorage 还是 Cookie?
没有脱离场景的标准答案。localStorage 方便但暴露给 XSS 读取;HttpOnly Cookie 降低脚本直接窃取风险,却要求认真处理 CSRF 和跨站策略。选择时应结合应用的 XSS 防护、跨域需求、客户端类型和团队维护能力。
JWT 为什么还需要查数据库?
JWT 可以减少“根据 Session ID 查询基础身份”的需要,但不能取代所有服务端状态。服务仍可能查询账号是否被封禁、权限是否变化、令牌是否撤销,以及当前用户是否拥有某个订单或项目。无状态只描述认证状态的管理方式,不代表整个业务系统不需要数据库。
总结:先理解三段,再划清边界
JWT 是一种紧凑、自包含、可验证的网络令牌格式。它通过 Header.Payload.Signature 传递声明:Header 描述算法,Payload 承载 Claims,Signature 检测篡改并证明签发者掌握密钥。

使用 JWT 时,请记住四个结论:
- Base64URL 不是加密,Payload 不要放敏感信息。
- 签名验证通过不等于授权通过,还要检查
iss、aud、exp和权限范围。 - Bearer Token 泄露后可被重放,必须使用 HTTPS、短过期和安全存储。
- JWT 天然不擅长立即注销;需要撤销时,应明确引入黑名单、版本号或服务端状态。
JWT 的核心价值不是“完全不查数据库”,而是让服务在明确的信任边界内,用一串可验证的声明完成身份传递。先判断是否需要无状态令牌,再设计签名、存储、过期和撤销策略,才能真正把 JWT 用在合适的地方。

