JWT结构与使用场景
候选人小王在面试某家SaaS公司时,面试官翻到简历上"使用JWT做用户认证"这一行,开口问道:
"JWT是怎么组成的?Token过期了怎么办?"
小王说:"JWT就是一段字符串,分成三部分,用点号隔开。过期了就让用户重新登录..."
面试官继续追问:"那Token被盗了怎么办?用户怎么主动注销Token?"
小张开始支支吾吾...
从一个问题开始
小王的回答暴露了很多初学者对JWT的典型理解:知道它是"一段字符串",知道"过期就重新登录",但不知道它背后到底是怎么工作的,也不知道它的局限性在哪里。
JWT看起来很简单,但它的设计哲学和使用边界,比大多数人想象的要复杂得多。
今天这篇文章,就是帮你把JWT从"会用"升级到"理解原理、知道边界"。
【直观类比】
JWT像一张自带防伪的通行证
想象你去游乐园,买了张通行证(Token)。
这张通行证上印着:
- 正面信息:你的名字、有效日期、允许进入的区域(Payload)
- 防伪签名:游乐园官方盖的章,能证明这张票是真的(Signature)
检票员不需要去数据库查你的购票记录,只需要:
- 看正面信息是否有效
- 验一下印章是不是真的
JWT就是这样的工作原理——它是自包含的,验证方不需要存储任何会话信息。
这就是JWT最核心的优势:无状态验证。服务端不需要维护会话存储,Token本身包含了验证所需的所有信息。
但是...
游乐园通行证有个问题:一旦发给游客,你就收不回来了。
如果你的票丢了,任何捡到的人都可以用。游客想要"注销"这张票?抱歉,只能等它过期。
这和JWT的"被动失效"问题是一模一样的。
核心原理
JWT的完整结构
JWT由三部分组成,用点号(.)连接:
这三部分分别是:Header(头部)、Payload(载荷)、Signature(签名)。
Header:声明类型和算法
然后用Base64URL编码:
Base64不是加密!Header部分是明文的,任何人都能解码看到内容。所以不要在Payload里放敏感信息,比如密码。
Payload:存放声明
Payload里放的是Claims(声明),分为三种类型:
常用的注册声明(Registered Claims):
然后同样用Base64URL编码:
Signature:防伪签名
签名才是JWT的核心防伪机制:
签名的作用:如果Header或Payload被篡改,签名验证就会失败,因为攻击者没有密钥,无法生成新的有效签名。
完整JWT生成流程
签名算法:HS256 vs RS256
这是面试中经常被问到的问题,也是选型时的关键决策点。
HS256:对称签名
优点:简单,高性能 缺点:所有能验证Token的服务必须知道密钥。如果有多个服务,任何一个服务泄露密钥,整个系统都不安全。
适用场景:单体应用、微服务内部通信、团队内部共享密钥的场景。
RS256:非对称签名
优点:私钥只在签发服务器上,公钥可以公开给所有验证服务。即使验证服务被攻破,攻击者也只知道公钥,无法伪造Token。
适用场景:跨服务认证、开放API、第三方应用集成。
OAuth2.0的Access Token通常用RS256,因为授权服务器和资源服务器可能是不同的团队甚至不同的公司。
边界与特例
Token过期了怎么办?
Token过期后,用户需要重新获取。有几种常见策略:
方案1:让用户重新登录(最简单)
- 缺点:体验差
- 适用:安全性要求高、用户操作不频繁的场景
方案2:Refresh Token
方案3:Sliding Window / Fixed Window
- Sliding Window:每次使用Token时,如果发现剩余有效期不足一半,就续期
- Fixed Window:固定时间点续期,比如每天0点
Token被盗了怎么办?
这是JWT最大的痛点:Token一旦签发,服务端无法主动撤销。
解决方案:
- 短期Token:Access Token有效期设短一点(15分钟),即使被盗,损失也有限
- Token黑名单:维护一个Redis黑名单,存储已撤销Token的jti。但这违背了"无状态"的初衷
- 双Token + 用户修改密码:用户改密码时,使 Refresh Token失效
- Token版本号:在Payload中加一个
version字段,用户注销时递增版本号
如果你需要"用户注销后立即失效"这样的强需求,JWT可能不是最佳选择,建议用Session。
JWT的Payload能被篡改吗?
不能。签名是基于Header和Payload计算的,任何修改都会导致签名验证失败。
但是,Payload是Base64编码,不是加密。所以:
- ✅ 攻击者不能修改Payload(签名会不匹配)
- ✅ 攻击者不能伪造Token(没有密钥)
- ⚠️ 攻击者可以看到Payload的内容(Base64解码即可)
不要在JWT Payload中存储任何敏感信息,比如密码、身份证号、银行卡号。
常见误区
误区1:JWT比Session更安全
JWT和Session是两种不同的会话管理方案,不是谁比谁更安全的问题。
- Session把会话存在服务端,Token存在客户端
- JWT本身也是一种Token,验证时可能需要查黑名单
- 没有绝对更安全的方案,只有更适合场景的方案
误区2:JWT Payload是加密的
不是。JWT的Header和Payload只是Base64编码,任何人都能解码看到内容。签名只是防止篡改,不防止读取。
误区3:Token存Cookie比存LocalStorage更安全
存Cookie:
- ✅ 可以设置
HttpOnly防止XSS读取 - ✅ 可以设置
Secure只在HTTPS下传输 - ⚠️ 容易受到CSRF攻击
存LocalStorage:
- ✅ 不受CSRF影响
- ⚠️ 容易被XSS攻击窃取
移动端App建议用Keychain/Keystore,比Web存储更安全。
误区4:永远不要过期
很多初学者为了"永不过期",干脆不设置exp。
这是严重的安全漏洞。如果Token被盗,攻击者就拥有了永久访问权限。
合理的过期时间:
- Access Token:15分钟~1小时
- Refresh Token:1天~7天
- API Token:根据业务场景,可以长一些
记忆技巧
口诀
JWT三部分,头部声明算法,载荷放声明,签名来防伪 HS256对称密钥,团队内部用,RS256公私钥,开放API用 Payload不是加密,敏感信息别放里 Token要短期,被盗才不慌,长期用Refresh
JWT vs Session速查
实战检验
检验问题1
问题:前端每次请求都要带Token,你是怎么传递的?
常见错误答案:
- 直接放URL参数(
?token=xxx)—— 会被浏览器历史记录、服务器日志记录 - 放LocalStorage —— 可能被XSS攻击窃取
推荐答案:
- 放
Authorization: Bearer <token>请求头(最佳) - 放Cookie(配合HttpOnly、Secure、SameSite)
检验问题2
问题:用户在多个设备登录,怎么实现"单点登录"?
分析:
- Session方案:在Redis中以用户ID为Key存储会话列表,注销时删除所有
- JWT方案:维护Token黑名单,或用Refresh Token关联所有设备会话
检验问题3
问题:微服务架构下,API网关怎么验证JWT?
推荐架构:
授权服务器用RS256签发Token,公钥公开。API Gateway拿到公钥后可以验证Token,无需查询授权服务器。
【面试官心理】
面试官问JWT,其实是在测试你对"无状态认证"的理解深度。知道JWT三部分结构是60分,知道HS256/RS256区别是80分,知道JWT的局限性和适用场景是90分,如果还能说出撤销机制和微服务认证方案,那就是P7的水平了。
延伸阅读
- JWT与Session对比 - 了解两种认证方案的取舍
- OAuth2.0授权流程 - 了解JWT在OAuth2中的使用
- XSS攻击与防护 - 了解Token被盗的常见途径