前一阵在内部项目里给新同事讲登录联调的流程,他对着返回的一长串token问我:“这个字符串我看着像乱码,前端要拿它做什么?”我说你先把这个串丢到jwt.io上看一眼,他看完之后恍然大悟。其实很多人每天都在用JWT做登录验证,但真被问到“这个token是怎么拼出来的、为什么这么设计、解析的时候到底做了什么”,能一次讲清楚的人并不多。这篇我用一个简单版的项目实践,把JWT的解析原理和实战编码整个过一遍,适合刚开始接触token认证、或者已经在用但没细究过原理的同学参考。
我尽量不堆理论,从“拆开看结构”开始,到“手写一个工具类”,再到项目里登录验证和续签的常见套路,最后把容易踩的坑也整理出来。你可以照着代码直接改,也能通过这篇文章把JWT的运行机制弄明白。
1. JWT到底是什么:先把结构拆明白
1.1 三段式的身份令牌
JWT的全称是JSON Web Token,从名字就能看出来,它是用JSON格式组织的、在网络环境中传递的令牌。它不是一个加密后的密文,而是一段结构清晰的字符串,按照“头部.载荷.签名”的方式分成三段,中间用英文句点隔开。
一个典型的长这样:
code复制eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwMDEsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDAwMzYwMH0.KVn3e2jA1k98KLVLqwH4dArWjI7lQqgZzY6QB0aJ8Zo
- 第一段是Header,里面记录了签名算法和token类型;
- 第二段是Payload,存放业务需要的信息,比如用户ID、用户名、过期时间等;
- 第三段是Signature,把前两段内容用一个密钥(或私钥)做签名后得到的值。
我经常用快递单来类比这段结构:Header相当于快递单上的“运输方式说明”,Payload是包裹里的商品信息,Signature是快递盒上的防拆封条。盒子是透明的,谁都能看到商品是什么,但一旦有人动过商品,防拆封条就会对不上,收件方就能判断出这个包裹被改过。这个理解方式,在讲JWT的时候特别好用,新同事一下就明白了。
1.2 为什么服务端不存SESSION也认识你
传统Web应用里,登录状态通常保存在服务端Session里,用户拿着一个sessionId,服务端每次请求都去内存里查一下这个sessionId有没有、过期没有。这在单机时代很顺手,但到了微服务架构、前后端分离、多端登录的场景,问题就来了:
- 服务端要维护大量session,重启之后就丢了;
- 多个服务实例之间要同步session,不然用户在A机器登录,请求却发到B机器就识别不了;
- 移动端和浏览器端的会话管理方式还不一样,处理起来很繁琐。
JWT的思路完全不同。它把“用户是谁、什么时候过期”这些信息直接编码进token本身,并且用签名保证内容没被篡改。服务端拿到token后只需要做三件事:
- 解析出Header和Payload;
- 用自己持有的密钥去验证签名;
- 校验过期时间是否还在有效期内。
整个过程不依赖任何存储。服务端是无状态的,哪个节点收到请求都能独立完成验证,这正是分布式架构下大家倾向于用JWT做登录认证的核心原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解析JWT:在线工具和手动解析的原理
2.1 jwt.io这类在线工具到底做了什么
很多人第一次认识JWT就是通过jwt.io这个在线解析网站,把token粘进去,右侧立刻显示JSON格式的Header和Payload。看起来高大上,其实它做的事情极其简单:把token按句点切分成三段,然后把第一段和第二段做Base64Url解码。
这里有个关键点容易忽略,JWT用的是Base64Url编码,而不是我们平时做图片传输时常用的Base64标准编码。两者差别在于URL安全处理:标准Base64里的+、/、=三个字符放到URL里会造成歧义,所以JWT规定把+换成-,把/换成_,并且去掉末尾的=号。
我在没注意这个细节之前,拿着解析出来的内容总感觉对不上,检查好久才发现是自己手写的Base64解码工具没切换成URL安全模式。所以如果你打算自己动手写一个JWT解析函数,第一个要注意的点就是:先处理字符映射关系,再去做解码。
在线工具只帮你解到这一步。Payload和Header本身就是明文编码的,不是加密内容,任何能解码Base64Url的人都能看到其中的业务信息。签名段它不会帮你“验签”,因为要做到验签必须知道密钥,在线工具没有你的密钥,自然只能做无密钥的解码。你要判断token是真的还是伪造的,必须在自己的服务端用密钥验签,这个后面会专门写。
2.2 手动解析一遍你就理解得更深
我建议每个做后端开发的同学都自己实现一次解析,哪怕只是玩具级代码。用Java举个例子,手动拆解JWT大概长这样:
java复制public class SimpleJwtParser {
public static void main(String[] args) {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
+ "eyJ1c2VySWQiOjEwMDEsInVzZXJuYW1lIjoiYWRtaW4ifQ."
+ "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
// 1. 按句点切分
String[] parts = token.split("\\.");
if (parts.length != 3) {
throw new IllegalArgumentException("token格式不正确");
}
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]));
String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]));
System.out.println("Header: " + headerJson);
System.out.println("Payload: " + payloadJson);
}
}
运行这段代码,你会看到Header输出类似这样的内容:
json复制{"alg":"HS256","typ":"JWT"}
Payload输出:
json复制{"userId":1001,"username":"admin"}
看到这里你就会明白,所谓“在线解析”,本质就是手动切字符串加Base64Url解码。解析本身不需要密钥,因为内容本来就没加密。这也是为什么JWT规范一直强调:不要再Payload里放密码、手机号、身份证号这类敏感信息。谁拿到token都能看到这些字段,你就算把token缩短到三分钟有效期,泄露风险也一样存在。
3. 签名和校验:为什么token不能被随便改
3.1 签名是怎么算出来的
用户登录成功后,服务端把用户信息放进Payload,然后拼接Header和Payload,用约定好的算法对这段内容做签名计算,签名的输入大体上是这样的公式:
code复制signature = HMACSHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
secret
)
不同的算法对应不同的计算方式。HS256是HMAC-SHA256,使用同一个密钥做签名和验证,这种叫对称算法;RS256是RSA-SHA256,用私钥签名、公钥验证,非对称算法。
HMAC算法算是“带钥匙的哈希”。它会把密钥混进内容里,重复计算多次,最终输出一串固定长度的值。只要内容或者密钥任何一个发生变化,最终算出来的签名都会对不上。RSA则是公钥密码体系,私钥只有服务端持有,所以理论上无法被伪造签名。
3.2 服务端校验时做了什么
服务端收到一个token后,会取出来请求头里的Authorization字段,常见的格式是:
text复制Au
