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
}
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 过期了。你想想看,如果密钥泄露了,那整个认证体系就形同虚设。
4.3 JWT 在分布式系统中的应用
分布式系统里,JWT 最大的好处就是无状态。服务端不用存 Session,每个请求自己带着身份信息。
我画了一张图,帮你理解 JWT 在分布式架构中的流转过程:
流程其实很简单:
- 用户向认证服务发登录请求。
- 认证服务校验通过,签发 JWT 返回。
- 用户拿着 JWT 去访问业务服务 A。
- 业务服务 A 用公钥验签,通过则放行。
- 用户再去访问业务服务 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 的组合。这套方案在我经历过的几个高并发项目里,表现都很稳。你可以在自己的项目里试试看,有问题随时调整。