JWT 把身份声明打包成一个可自验证的字符串,让服务端不必保存会话就能确认”你是谁”。但便利背后有几个高频坑:Payload 只编码不加密、令牌签发后难以收回。本文从三段结构讲起,把签名原理、认证流程与吊销方案说透。

它解决什么问题

传统 Session 认证里,服务端要为每个在线用户保存一份会话,请求来了先查库或 Redis 才能确认身份;多实例部署还得解决会话共享问题。JWT 的思路是把身份信息和有效期写进令牌本体,再用密码学签名防篡改:任何持有密钥的服务都能独立完成校验,不需要中心化的会话存储,水平扩展、跨服务调用和移动端接入都随之简化。代价则是令牌在过期前始终有效,服务端失去了”一键踢人”的天然能力。

核心原理

三段式结构

JWT 形如 xxxxx.yyyyy.zzzzz,点号分隔三段,前两段是 JSON 经 Base64URL 编码的结果:

  • Header(头部):元信息,主要是令牌类型 typ 和签名算法 alg;
  • Payload(载荷):承载声明(Claims),如 sub(用户标识)、exp(过期时间)、iat(签发时间)、iss(签发方)、aud(接收方)及业务自定义字段;
  • Signature(签名):对前两段拼接结果做的密码学签名,用来发现任何改动。
{
"alg": "HS256",
"typ": "JWT"
}

这个 Header 编码后就是令牌第一段,完整令牌长这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNzAwMDAwMDAwfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

签名如何生成与校验

签名输入是编码后的 Header、Payload 和密钥,以 HS256 为例:

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)

服务端收到令牌后,用同样的算法和密钥对前两段重算签名,再与第三段做恒定时间比较。攻击者改了 Payload 里任何字符,都算不出匹配的签名——除非密钥泄露。这就是”防篡改”的全部含义。

关键认知:编码不等于加密

Base64URL 只是可逆编码,任何人拿到令牌都能解出前两段的明文。签名保护的是完整性,不是机密性,密码、手机号、身份证这类敏感信息绝不能放进 Payload。同理,能解析(decode)出内容不代表令牌可信——必须先验签,再校验 exp、nbf、iss、aud,全部通过才能采信。

深入机制

HS256 vs RS256:对称与非对称

  • HS256(HMAC-SHA256):同一把密钥签名和验签。部署简单、计算快,但每个能验签的服务都能伪造令牌,密钥在任一节点泄露即全线失守。
  • RS256(RSA-SHA256):私钥签名、公钥验签。验签方只持公钥,无法签发新令牌,密钥分发面大幅缩小,适合微服务、第三方接入和密钥轮换场景;代价是签名验签慢一个数量级,且要维护公钥分发机制(如 JWKS 端点)。

选型经验:签发与消费在同一信任边界内,HS256 够用;签发方与验证方分离,选 RS256 或 ES256。

完整认证流程

  1. 客户端提交用户名密码,服务端校验通过后签发 JWT;
  2. 客户端保存令牌,后续请求放在 Authorization: Bearer <token> 头中;
  3. 服务端按 kid 取对应密钥,先验签,再校验 exp、nbf、iss、aud;
  4. 全部通过后用 sub 建立用户上下文,授权层再结合具体资源判断权限。

注意:JWT 只证明”签发时刻声明是真的”。用户此后被禁用或权限调整,令牌不会感知,业务层仍需处理。

工程实践

吊销难题与两种主流解法

JWT 发出后到过期前天然有效,退出登录、封号、改密码都不会让它立即失效。两种主流补救:

  • 短有效期 + 刷新令牌:access token 只活 15~30 分钟,泄露窗口小;另发长寿命的 refresh token 用于换新,由服务端管理、可撤销可轮换。这是业界事实标准。
  • 黑名单:把被吊销令牌的 jti 写入 Redis,过期时间设为剩余寿命,验签先查黑名单。能即时吊销,但每次请求多一次查询,等于部分放弃无状态。

实践中常两者结合:常态靠短令牌压缩风险窗口,高危操作再写黑名单兜底。

其他必做项

  • 服务端固定算法白名单,绝不信任令牌自报的 alg,拒绝 none,防 HS256/RS256 算法混淆攻击;
  • 全程 HTTPS;Payload 只放最小必要声明,控制令牌体积;
  • 使用成熟开源库完成签发与验签,不手写密码学逻辑;时钟偏差留 30~60 秒容忍,密钥按 kid 轮换。

对比与权衡

维度 HS256(对称) RS256(非对称)
密钥形态 共享密钥,签名验签通用 私钥签名,公钥验签
验签方能力 持有密钥即可伪造令牌 只有公钥,无法签发
性能 快,HMAC 开销极小 慢约一个数量级
适用场景 单体或同一信任域内 微服务、跨组织、公钥轮换
密钥泄露影响 任一验证节点泄露即全线失守 只需死守签发方私钥

面试视角

JWT 的三段结构是什么?签名防什么?

Header 记算法与类型,Payload 放声明,Signature 是对前两段的密码学签名。签名防篡改不防窃听:前两段只是编码,谁都能解开,敏感信息不能入 Payload。

HS256 和 RS256 怎么选?

签发与验证在同一信任域内,用 HS256 简单高效;两者分离(微服务、开放 API)时用 RS256,验证方只拿公钥,私钥泄露面最小。

用户退出登录后 JWT 还有效,怎么办?

根源是无状态。主流做法是短寿命 access token 加可撤销的 refresh token,需即时生效的场景(封号、改密)再把 jti 写进 Redis 黑名单。只发长令牌又不留任何服务端记录,是典型设计缺陷。

能解码出 Payload 就说明令牌可信吗?

不能。解码不需要密钥,任何人都能做,不含任何信任。必须先验签,再校验 exp、iss、aud 等声明,全部通过才能采信。

小结

  • JWT 用”自包含声明 + 签名”换取服务端无状态,本质是拿吊销能力换扩展性。
  • 三段结构里只有签名参与密钥运算;Base64URL 是编码不是加密,Payload 视同明文。
  • HS256 与 RS256 的分野在于验签方是否可信;跨信任边界一律上非对称算法。
  • 生产可用的 JWT 体系 = 短有效期 + 刷新令牌 + 算法白名单 + HTTPS,即时吊销再叠加黑名单。