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里的数据任何人都能解码看到,所以别放敏感信息。我曾经见过有人把用户密码哈希直接放进去,这操作真是...嗯,不推荐。

⚠️ 重要提醒:Payload是Base64编码,不是加密。任何人都可以解码查看内容。绝对不要存放密码、信用卡号等敏感信息!

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吧。

💡 小技巧:使用HS256时,密钥要足够复杂,至少32个字符。我一般用openssl rand -base64 32生成。

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大(每次请求都要传)
⚠️ 避坑指南:我曾经在一个项目中用了JWT,但没处理好token过期的问题。用户登录后token有效期设了7天,结果用户改了密码,但旧的token还能用。后来我加了token版本号机制,每次改密码就递增版本号,服务器验证时检查版本号是否匹配。

实际项目怎么选?

我的经验是:

  • 传统Web应用,用户量不大 → Session
  • 前后端分离、移动端为主 → JWT
  • 微服务架构 → JWT(配合RS256)
  • 需要实时撤销用户权限 → Session或JWT+黑名单

其实很多项目是混合使用的。比如用JWT做API认证,用Session做管理后台。没有银弹,适合你的才是最好的。

💡 我的建议:如果你刚开始做项目,可以先从JWT入手。因为现在前后端分离是主流,JWT的跨域支持更好。但一定要处理好token刷新和撤销的问题,不然上线后有你头疼的。

好了,JWT的原理和对比就讲到这里。下一章我会带你手写一个JWT的签发和验证流程,到时候你会对签名算法有更直观的理解。