JWT原理:JWT结构解析与签名算法
聊到JWT,很多新手第一反应就是「哦,那个token」。但说真的,如果你只把它当token用,那可就大材小用了。我最早接触JWT是在做一个移动端项目,那时候还在用传统的session方案,结果被跨域问题折腾得够呛。后来换了JWT,整个世界清净了。
JWT全称是JSON Web Token,说白了就是一种紧凑的、自包含的令牌格式。什么叫自包含?就是令牌本身携带了所有需要的信息,不需要再去查数据库。这一点跟session完全不同,后面我会详细对比。
JWT结构解析:三部分各司其职
一个JWT长什么样?我给你看个例子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
看到没?三个部分用点号隔开。分别是:
- Header(头部):令牌类型和签名算法
- Payload(载荷):实际携带的数据
- Signature(签名):防篡改的保证
嗯,这里要注意,这三部分都是Base64URL编码的,不是加密。很多人以为JWT是加密的,其实不是。你拿Base64解码就能看到明文。所以千万别把密码之类的敏感信息放进去!
Header:告诉别人我怎么签的
Header通常长这样:
{
"alg": "HS256",
"typ": "JWT"
}
alg就是签名算法,typ就是令牌类型。我见过有人把typ写成别的,结果解析器不认。所以这个字段最好别乱改。
Payload:真正干活的部分
Payload里放的是声明(claims),分三种:
- 注册声明:预定义的字段,比如iss(签发者)、exp(过期时间)、sub(主题)
- 公共声明:大家约定好的字段,比如用户ID、角色
- 私有声明:你自己定义的字段,比如nickname、avatar
举个例子:
{
"sub": "1234567890",
"name": "张三",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
我个人习惯把用户ID放在sub里,角色信息放在自定义字段里。但注意,Payload里的数据任何人都能解码看到,所以别放敏感信息。我曾经见过有人把用户密码哈希直接放进去,这操作真是...嗯,不推荐。
Signature:防篡改的最后防线
Signature是怎么生成的?简单说就是:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
服务器用密钥对前两部分进行签名,生成第三部分。客户端拿到JWT后,服务器重新计算签名,如果一致,说明数据没被篡改过。
这里有个坑:签名只能保证数据没被改过,但不能保证数据是保密的。所以Payload里的信息还是能被看到。我刚开始做的时候也犯过这个迷糊,以为签名了别人就看不懂了。
JWT签名算法:HS256 vs RS256
签名算法这块,我见过不少团队选错。说白了,HS256和RS256最大的区别就是:HS256是对称加密,RS256是非对称加密。
| 特性 | HS256 | RS256 |
|---|---|---|
| 密钥类型 | 对称密钥(同一个密钥签名和验证) | 非对称密钥(私钥签名,公钥验证) |
| 性能 | 快 | 慢(签名慢,验证快) |
| 适用场景 | 单一服务、内部系统 | 微服务、第三方认证 |
| 密钥管理 | 简单,但密钥泄露风险高 | 复杂,但更安全 |
HS256:简单粗暴,适合小项目
HS256用同一个密钥来签名和验证。你想想看,如果密钥泄露了,任何人都能签发合法的JWT。所以密钥保管特别重要。
我个人的建议是:如果你只有一个后端服务,或者服务之间都在内网通信,用HS256就够了。但如果你有多个微服务,或者要给第三方提供API,那还是用RS256吧。
RS256:微服务架构的首选
RS256用私钥签名,公钥验证。这样有个好处:只有认证服务持有私钥,其他服务只需要公钥就能验证JWT的合法性。
我在一个微服务项目里用过RS256。认证中心签发JWT,其他服务拿到公钥后直接验证,不需要每次都去认证中心查。这样既减少了网络开销,也提高了系统的可用性。
但RS256也有缺点:签名比HS256慢。不过验证速度很快,所以影响不大。
- 单体应用、内部系统 → HS256
- 微服务架构、开放API → RS256
- 性能敏感场景 → HS256
- 安全要求高 → RS256
JWT vs Session:到底选哪个?
这个问题我经常被问到。说实话,没有绝对的好坏,只有适不适合。
Session:传统但可靠
Session的工作原理是:用户登录后,服务器生成一个session ID,存在内存或Redis里,然后把session ID通过cookie发给客户端。客户端每次请求都带上这个cookie,服务器去查session数据。
优点很明显:
- 可以随时撤销session(删掉服务器端的数据就行)
- 数据安全(存在服务器端)
- 成熟稳定,文档丰富
缺点也很明显:
- 服务器要维护session状态,扩展起来麻烦
- 跨域问题(cookie跨域需要额外配置)
- 移动端支持不好(很多移动端不自动处理cookie)
JWT:无状态但需谨慎
JWT的优点:
- 无状态,服务器不需要存储会话信息
- 跨域友好,可以放在Authorization头里
- 移动端和Web端都能用
缺点:
- 签发后无法撤销(除非用黑名单机制)
- Payload数据可见(别放敏感信息)
- 体积比session ID大(每次请求都要传)
实际项目怎么选?
我的经验是:
- 传统Web应用,用户量不大 → Session
- 前后端分离、移动端为主 → JWT
- 微服务架构 → JWT(配合RS256)
- 需要实时撤销用户权限 → Session或JWT+黑名单
其实很多项目是混合使用的。比如用JWT做API认证,用Session做管理后台。没有银弹,适合你的才是最好的。
好了,JWT的原理和对比就讲到这里。下一章我会带你手写一个JWT的签发和验证流程,到时候你会对签名算法有更直观的理解。