← 返回文章列表

JWT 的三段结构里装了什么(以及三个致命误区)

JWT编码避坑

三段结构

JWT 由 header.payload.signature 三段组成,用点号连接。前两段是 Base64Url 编码的 JSON——只是编码,不是加密:任何人复制出去解码就能看到内容。第三段是签名,用来验证前两段没有被改动。

实际长什么样

// header(解码后)
{ "alg": "HS256", "typ": "JWT" }

// payload(解码后)—— 这些字段任何人可见
{ "sub": "user_1024", "role": "admin", "iat": 1758000000, "exp": 1758003600 }

// 最终令牌
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEwMjQifQ.3f9c...(签名)

三个致命误区

  1. 把敏感信息放进载荷:手机号、身份证号、内部权限细节放进 payload,等于明文公开;JWE 才是加密方案;
  2. 只在前端解码就信任身份:前端解出的 role: admin 毫无意义,服务端必须验签(并校验 iss、aud、exp);
  3. 不校验算法与有效期:忽略 alg 会遭遇 alg: none 或算法混淆攻击;不校验 exp 则过期令牌永久有效。

工程上会遇到的问题

  • 无法主动失效:JWT 自包含,签发后难以撤销;常见做法是短有效期(5–15 分钟)+ refresh token,必要时维护黑名单;
  • 体积膨胀:把大量权限塞进 payload,会让每个请求头都变大,注意 4KB 级别的常见上限;
  • 时钟偏移:客户端与服务器时间不一致会导致“刚签发就过期”,校验时可留几十秒容差。

常见问题

JWT 和 Session 怎么选?无状态、多服务横向扩展的场景适合 JWT;需要即时撤销、权限频繁变化则用服务端 Session 更简单。签名用什么算法?单服务共享密钥用 HS256,多方验签用 RS256/ES256(公钥验签、私钥签发)。

JWT 与服务端 Session 的取舍

维度JWT服务端 Session
状态无状态,服务端不保存会话服务端保存会话
撤销困难,需要黑名单或短有效期立即可撤销
横向扩展简单,无需共享存储需要共享存储或粘性会话
请求开销令牌较大,每次请求携带只携带一个会话标识

实践中最常见的是折中方案:短期访问令牌(JWT) + 可撤销的刷新令牌(服务端存储),既保留无状态的扩展优势,又能在必要时立即失效。

实战案例:三个真实的安全事故

  1. “把 alg 改成 none 就能绕过校验”:早期库若信任头部里的 alg,攻击者可伪造任意令牌。修复:服务端固定允许的算法,绝不信任头部。
  2. “HS256 的密钥用了示例默认值”:很多人拿文档里的 secret 直接上线,等于没有签名。修复:使用足够长、随机且不明文入库的密钥,并定期轮换。
  3. “令牌无法撤销”:JWT 是无状态的,签发后在过期前一直有效。修复:缩短有效期 + 刷新令牌,或维护服务端黑名单 / 版本号。

常见问题(FAQ)

JWT 是加密的吗?默认(JWS)不是,只签名不加密,负载可被任何人 Base64 解码读取,切勿放敏感信息。能放权限吗?可以,但权限变更后旧令牌仍带旧权限,需靠短有效期或版本号兜底。存在哪里?localStorage 易受 XSS,HttpOnly Cookie 更稳但要防 CSRF;按威胁模型权衡。一定要用 JWT 吗?不一定,服务端会话在很多场景更简单可控。

动手试试:Base64 编解码、哈希计算

算法混淆与其它攻击面

  • 算法混淆:若校验方接受令牌头部指定的算法,攻击者可把 RS256 改成 none 或用公钥当 HMAC 密钥,服务端必须固定允许的算法白名单;
  • 拒绝 none:任何实现都不应接受无签名令牌;
  • 校验全部声明:iss、aud、exp、nbf 都要校验,只验签名等于只看“没被改过”;
  • 密钥要够强:HS256 的密钥若过短可被离线爆破,应使用足够长的随机密钥;
  • 别放敏感数据:载荷只是 Base64 编码而非加密,任何人都能读取。

与 Session 的取舍

JWT 的优势是无状态与跨服务传递,代价是难以即时吊销。折中做法是使用短期访问令牌(5–15 分钟)配长效刷新令牌,刷新令牌存服务端并支持吊销;这样既减少查库,又保留“立即下线”的能力。