4、JWT原理与实战:从结构到安全

JWT,全称 JSON Web Token,现在几乎是分布式认证的标配了。我记得刚接触它的时候,觉得这东西不就是一段加密字符串嘛,有啥好研究的?后来在线上被坑过几次,才老老实实把它的每个字节都搞明白。

这一章,我会把 JWT 的结构、签名验签、分布式玩法以及那些容易踩的坑,一次性讲清楚。

4.1 JWT 长什么样?拆开看看

一个 JWT 长这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

用肉眼一看,三个部分用点号隔开。我习惯叫它们:头、体、签名。

部分 名称 内容 是否明文
第一部分 Header(头部) 算法类型、Token 类型 Base64 明文
第二部分 Payload(载荷) 用户信息、过期时间等声明 Base64 明文
第三部分 Signature(签名) 对前两部分的签名摘要 密文(依赖密钥)

Header 里一般长这样:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload 里放的是你真正关心的数据:

{
  "sub": "user_10086",
  "name": "张三",
  "role": "admin",
  "iat": 1700000000,
  "exp": 1700003600
}
注意:Payload 只是 Base64 编码,不是加密!谁拿到都能解码看到内容。我在项目中见过有人把密码明文放进去……嗯,那场面,不太好看。

4.2 签名与验签:到底怎么保证可信?

签名是 JWT 的灵魂。说白了,就是服务端用密钥对前两部分算一个摘要,然后附在后面。客户端改不了,因为没有密钥。

我常用的签名算法有两种:

  • HS256(对称):同一个密钥既签名又验签。适合单体应用或内部服务。
  • RS256(非对称):私钥签名,公钥验签。适合微服务、第三方分发。

来个代码示例,用 Node.js 生成和验证 JWT:

const jwt = require('jsonwebtoken');

// 生成 Token
const token = jwt.sign(
  { userId: 10086, role: 'admin' },
  'my_secret_key',
  { expiresIn: '1h' }
);

// 验证 Token
try {
  const decoded = jwt.verify(token, 'my_secret_key');
  console.log('验证通过:', decoded);
} catch (err) {
  console.log('验证失败:', err.message);
}

验签失败的原因,我遇到过最多的就两个:密钥不对,或者 Token 过期了。你想想看,如果密钥泄露了,那整个认证体系就形同虚设。

我的习惯:生产环境用 RS256。私钥放认证中心,公钥分发给各个微服务。这样即使某个服务被攻破,也拿不到签名私钥。

4.3 JWT 在分布式系统中的应用

分布式系统里,JWT 最大的好处就是无状态。服务端不用存 Session,每个请求自己带着身份信息。

我画了一张图,帮你理解 JWT 在分布式架构中的流转过程:

JWT 在分布式系统中的认证流程 用户 认证服务 业务服务A 业务服务B 1. 登录请求 2. 返回 JWT 3. 携带 JWT 请求 4. 携带 JWT 请求 公钥分发 说明:业务服务只验签不签名,公钥即可完成验证。 认证服务持有私钥,是唯一能签发 Token 的节点。

流程其实很简单:

  1. 用户向认证服务发登录请求。
  2. 认证服务校验通过,签发 JWT 返回。
  3. 用户拿着 JWT 去访问业务服务 A。
  4. 业务服务 A 用公钥验签,通过则放行。
  5. 用户再去访问业务服务 B,同样的 JWT,同样的验签流程。

每个业务服务不需要共享 Session,也不需要调认证服务接口来校验。这就是 JWT 在分布式系统里最爽的地方——去中心化认证。

4.4 常见安全问题与避坑指南

JWT 虽然好用,但坑也不少。我曾经在线上遇到过几个经典问题,拿出来给你提个醒。

4.4.1 密钥泄露

密钥一旦泄露,攻击者可以伪造任意身份的 Token。我建议:

  • 使用 RS256 非对称算法,私钥严格保管。
  • 定期轮换密钥,旧密钥保留一个缓冲期。
  • 不要硬编码在代码里,用配置中心或密钥管理服务。

4.4.2 Token 被盗用

JWT 一旦签发,除非过期,否则一直有效。如果被中间人截获,就能冒充用户。

  • 强制使用 HTTPS,防止中间人攻击。
  • 设置合理的过期时间,我一般设 15-30 分钟。
  • 敏感操作(如修改密码)要求二次验证。

4.4.3 注销难题

JWT 是无状态的,服务端没法主动让它失效。这是它天生的短板。

  • 维护一个黑名单(Redis 或数据库),每次请求检查。
  • 或者用短 Token + 长 Refresh Token 的方案。
  • 实在不行,就降低过期时间,让 Token 快速失效。

我曾经踩过的坑:有一次把 JWT 的过期时间设成了 7 天,结果一个员工离职后,他的 Token 还能用。从那以后,我所有项目的 Token 过期时间都不超过 1 小时,配合 Refresh Token 实现无感续期。

4.4.4 算法混淆攻击

攻击者把 Header 里的 alg 改成 none,然后去掉签名部分。如果服务端没校验算法,就会直接放行。

// 错误做法:直接解码不验签
const decoded = jwt.decode(token);

// 正确做法:必须验签
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

嗯,这里要注意:永远不要信任客户端传来的任何东西。验签是底线,不能省。

4.5 总结一下

JWT 的核心就三件事:结构、签名、安全。结构搞清楚三段分别存什么;签名选对算法,对称还是非对称看场景;安全方面,密钥管理、过期时间、注销机制,一个都不能少。

我个人习惯在生产环境用 RS256 + 短过期 + Refresh Token 的组合。这套方案在我经历过的几个高并发项目里,表现都很稳。你可以在自己的项目里试试看,有问题随时调整。