news 2026/11/4 12:09:15

JWT 入门:结构是什么、jjwt 怎么用、有哪些坑要注意

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JWT 入门:结构是什么、jjwt 怎么用、有哪些坑要注意

JWT 入门:结构是什么、jjwt 怎么用、有哪些坑要注意

📌 先说结论

JWT(JSON Web Token)就是一段经过签名的字符串,用来在客户端和服务端之间传递身份信息。

它解决什么问题?传统 Session 需要服务端存一份登录状态,多台服务器还要共享。JWT 把信息直接编码在 token 里,服务端不用存,只要验签就知道你是谁。

三句话记住它的特性:

  1. 不是加密的—— 里面的内容谁都能解码看到,只是防篡改(改了签名就对不上)
  2. 无状态—— 服务端不存,所以没法主动让某个 token 立即失效
  3. 靠签名验证—— 服务端用密钥重新算一遍签名,对得上才认

一、JWT 长什么样

1.1 三段结构

一个 JWT 就是三个用.连接的字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIiwibmFtZSI6IuW8oOS4iSJ9.xxx └──────── header ────────┘ └──────── payload ────────┘ └─ signature ─┘
段内容举例
Header用什么算法、什么类型{"alg":"HS256","typ":"JWT"}
Payload存放的数据{"sub":"1001","name":"张三"}
Signature签名,防止篡改对前两段用密钥算的 HMAC

1.2 前两段可以直接解码看

这是最重要的一点:header和payload都只是 Base64 编码,不是加密。

用 jjwt 生成一个带手机号的 token,然后直接解码 payload:

Stringtoken=Jwts.builder().subject("1001").claim("phone","13800138000")// ← 塞了手机号.signWith(KEY).compact();// 不验签,直接解码第二段Stringpayload=newString(Base64.getUrlDecoder().decode(token.split("\\.")[1]));System.out.println(payload);

实测输出:

直接解码: {"sub":"1001","phone":"13800138000"} → 手机号明文可见!签名只防篡改,不防窥探

⚠️绝对不要在 payload 里放密码、手机号、身份证、银行卡号。任何拿到 token 的人都能看到。

1.3 签名在干什么

signature = HMAC-SHA256(header + "." + payload, 密钥)

服务端验证的流程:

拿到 token → 拆三段 → 用密钥重新算一遍签名 → 和自己带的签名对比 ├─ 一致 → 内容没被改过,可信 └─ 不一致 → 有人动过手脚,拒绝

关键:服务端不需要存任何东西,只要密钥在,就能独立验证。这就是"无状态"。


二、jjwt 怎么用(最常用的库)

Java 里最流行的是jjwt。下面全是实测通过的代码。

2.1 加依赖(Maven)

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.12.6</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.12.6</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.12.6</version><scope>runtime</scope></dependency>

💡三个包都要:jjwt-api是接口,jjwt-impl是实现,jjwt-jackson负责 JSON 序列化。只加 api 会运行报错(我实测过,会抛ServiceConfigurationError)。

2.2 生成密钥

importio.jsonwebtoken.security.Keys;importjavax.crypto.SecretKey;// 生成一个符合 HS256 要求的密钥(256 位)SecretKeyKEY=Keys.secretKeyFor(SignatureAlgorithm.HS256);

⚠️密钥必须够长。HS256 要求至少 256 位(32 字节),用短密钥会直接抛WeakKeyException。

生产环境不要把密钥硬编码在代码里,应该放配置中心或环境变量。

2.3 生成 token

importio.jsonwebtoken.Jwts;importjava.util.Date;Stringtoken=Jwts.builder().subject("1001")// sub:用户ID.claim("name","张三")// 自定义字段.claim("role","admin").issuedAt(newDate())// iat:签发时间.expiration(newDate(System.currentTimeMillis()+30*60*1000))// exp:30分钟后过期.signWith(KEY)// 用密钥签名.compact();// 生成 token 字符串System.out.println(token);

2.4 验证 token

importio.jsonwebtoken.*;importio.jsonwebtoken.security.Keys;JwtParserparser=Jwts.parser().verifyWith(KEY)// 用同一个密钥验签.build();Claimsclaims=parser.parseSignedClaims(token).getPayload();System.out.println(claims.getSubject());// 1001System.out.println(claims.get("name"));// 张三System.out.println(claims.get("role"));// admin

实测输出:

1) 生成和校验 token: token 长度: 148 字符 subject=1001 name=张三 role=admin

💡注意 API 版本:jjwt 0.12.x用了新 API(verifyWith/parseSignedClaims)。老教程里的setSigningKey/parseClaimsJws是 0.11 及以前的写法,在 0.12 里会编译报错。

2.5 处理验证失败

验签可能因为各种原因失败,必须区分处理:

try{Claimsclaims=parser.parseSignedClaims(token).getPayload();// 验证通过,正常业务}catch(ExpiredJwtExceptione){// token 过期了 → 让用户重新登录thrownewAuthException("登录已过期,请重新登录");}catch(SignatureExceptione){// 签名不对 → 有人篡改,或密钥不对thrownewAuthException("token 无效");}catch(JwtExceptione){// 其他 JWT 格式问题thrownewAuthException("token 格式错误");}

实测:过期和篡改分别抛什么异常

2) 过期时间: 30秒后过期: 通过 ✓ 已过期: 失败 → 已过期 3) 篡改检测: 正确密钥: 通过 ✓ 错误密钥: 失败 → SignatureException 篡改 payload: 失败 → SignatureException

⚠️千万别只写catch (Exception e)然后放行——验签失败必须拒绝请求,这是安全底线。


三、JWT 的标准字段

除了自定义字段,JWT 有一组约定的标准字段:

字段全称含义示例
subsubject主体,通常是用户 ID"1001"
expexpiration time过期时间(秒级时间戳)1790000000
iatissued at签发时间同上
ississuer签发者(哪个服务发的)"my-app"
audaudience接收方(给谁用)"web-client"
jtiJWT IDtoken 唯一标识(可用于黑名单)"uuid-1234"

代码示例:

Datenow=newDate();Stringtoken=Jwts.builder().subject("1001").issuer("my-app")// iss.audience().add("web-client").and()// aud.issuedAt(now)// iat.expiration(newDate(now.getTime()+30*60*1000))// exp.id("uuid-1234")// jti.signWith(KEY).compact();Claimsc=parser.parseSignedClaims(token).getPayload();System.out.println(c.getSubject());// 1001System.out.println(c.getIssuer());// my-appSystem.out.println(c.getAudience());// [web-client]System.out.println(c.getId());// uuid-1234

💡sub和exp是必用的:sub标识用户身份,exp控制有效期。jti在需要"主动踢人"时有用(见下一章)。


四、五个必须知道的坑

4.1 坑一:payload 放敏感信息

已在 1.2 说明。payload 是明文可读的,只放 userId、角色编码这类不敏感标识。

需要展示的用户信息,用 userId 去查缓存/数据库,不要塞 token 里。

4.2 坑二:token 没法主动失效(最头疼的坑)

这是 JWT 最大的架构代价。

用户点"退出登录",token 从客户端删了,但服务端并不知道——旧 token 在过期前仍然有效。

同样的问题:

  • 管理员封禁某用户,他的 token 还是有效的;
  • 用户改了密码,旧 token 依然能用;
  • token 泄露了,没法立刻作废。

三种主流解法:

方案做法代价
短有效期 + Refresh Tokenaccess token 5~15 分钟,refresh token 换新的需要维护 refresh 的存储
Redis 黑名单把要作废的jti存 Redis,验签后再查一次退化成"有状态"
token 版本号payload 放ver,改密码/封禁时数据库ver + 1每次请求多查一次版本号

💡实践最常见组合:短有效期(15 分钟)+ Refresh Token + 黑名单兜底。

不要指望 JWT 既能无状态又能即时吊销——这是协议本身的取舍。

4.3 坑三:验签失败却放行了

// ❌ 灾难写法:异常被吞掉,未登录也能访问try{Claimsclaims=parser.parseSignedClaims(token).getPayload();}catch(Exceptione){// 什么都不做,继续往下走 → 越权!}// ✅ 正确:验签失败必须中断try{Claimsclaims=parser.parseSignedClaims(token).getPayload();// 正常业务}catch(JwtExceptione){thrownewAuthException("token 无效");// 拒绝请求}

4.4 坑四:算法不固定,被alg=none绕过

攻击原理:JWT 的 header 是攻击者可写的。如果服务端"按 header 里声明的算法来验签",攻击者把alg改成none,某些库会认为"不需要验签"直接放行。

正确做法:算法由服务端固定,不看 header 的alg。

// ✅ jjwt 的 verifyWith(KEY) 已经强制用你指定的密钥,不受 header 影响JwtParserparser=Jwts.parser().verifyWith(KEY)// 固定密钥 = 固定算法.build();

💡用 jjwt 的正确 API 就天然安全。危险的是自己解析 header 再动态选算法的写法。

4.5 坑五:token 越用越长

payload 里塞的字段越多,token 越长。每次请求都要放在 HTTP Header 里,太长有风险:

  • 超过服务器 Header 大小限制(常见 8KB)→ 请求被拒;
  • 带宽浪费。

建议:payload 只放标识类信息(userId、角色、过期时间),不放完整用户对象。


五、JWT vs Session 怎么选

维度Session(服务端存)JWT(客户端存)
服务端存储有(内存/Redis)无
横向扩展需要共享存储✅ 天然支持
主动失效✅ 删 session 就行❌ 困难(要黑名单)
信息更新✅ 立即生效❌ 要等 token 过期
体积小(只传 sessionId)大(每次传整个 payload)
适合场景后台系统、要即时登出分布式、移动端、跨域

一句话选型:

需要"随时踢人下线" → Session;需要"多服务无状态验证" → JWT。

别被"无状态"忽悠:JWT 换掉的正是"即时可控性",换来的是"扩展性"。看你的业务更在乎哪个。


六、面试常问的 6 个问题

1. JWT 的三段结构是什么?哪一段加密了?

答:header.payload.signature。三段都没有加密——header 和 payload 只是 Base64URL编码(可直接解码看明文),signature 是对前两段的 HMAC/签名。JWT 提供的是完整性(防篡改),不是保密性。要加密得用 JWE。

2. JWT 怎么防止篡改?

答:靠签名。服务端用密钥对header.payload重新计算签名,和 token 里的签名比对。改了任何内容,签名就对不上。实测:篡改 payload 后验签抛SignatureException。

3. JWT 能主动失效吗?为什么?

答:原生不能。JWT 是无状态的,服务端不存任何东西,所以客户端删了 token 后服务端仍认旧 token 直到过期。解法:① 短有效期 + Refresh Token;② Redis 黑名单存被吊销的jti;③ payload 带版本号,吊销时数据库版本 + 1。

4.sub、exp、iat分别是什么?

答:sub(subject)主体标识,通常是用户 ID;exp(expiration time)过期时间戳;iat(issued at)签发时间。exp必须校验,注意客户端和服务端时钟偏差(一般容忍 30~60 秒)。

5. JWT 和 Session 怎么选?

答:JWT 无状态、易横向扩展,但不支持即时失效、每次请求携带体积大;Session 有状态、需要共享存储,但能即时踢人、信息更新立即生效。看重扩展性和跨域用 JWT;看重即时控制用 Session。

6.alg=none攻击是什么?怎么防?

答:攻击者在 header 里把alg改成none,诱导服务端跳过验签。防御原则是"算法由服务端固定,不信任 token 里的alg"。用 jjwt 的verifyWith(KEY)就是安全的(密钥固定了算法)。


七、总结

五句话:

  1. 结构——header.payload.signature,前两段明文可解码,签名只防篡改不防窥探。
  2. 用法—— jjwt 三件套依赖;Jwts.builder()生成,Jwts.parser().verifyWith(KEY).build()验证。
  3. 必用字段——sub(用户 ID)、exp(过期时间)。
  4. 最大的坑——无法主动失效,要配短有效期 + Refresh Token + 黑名单。
  5. 安全底线——验签失败必须拒绝,payload 不放敏感信息。

一句口诀:

JWT 是"自包含的通行证"——它证明你是谁,但不保证内容保密,也没法随时作废。


如果本文对你有帮助,欢迎点赞 + 收藏 + 关注!如果疑问或补充,欢迎在评论区交流~

关注我,后续分享更多 Java 核心知识点和实战技巧!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 12:07:09

四舍五入方法

double x 3.6; int ans (int)(x 0.5);

作者头像 李华
网站建设 2026/11/4 12:06:11

【Python】Python学习笔记-协程

00. 目录 文章目录00. 目录01. 什么是协程02. 协程函数和协程对象03. await关键字04. 多个任务同步执行05. 多个任务异步执行06. asyncio.gather07. 下载图片案例08. 附录01. 什么是协程 **概念&#xff1a;**协程&#xff08;Coroutine&#xff09;&#xff0c;是一种线程内部…

作者头像 李华
网站建设 2026/11/4 12:05:53

Agent 开发核心知识点:从 Chatbot 到会干活的模型

前言Chatbot 只会聊天&#xff0c;Agent 会调用工具、拆解任务、自己纠错。这一波 AI 应用落地的核心就是 Agent。这篇不讲虚的&#xff0c;讲清楚 Agent 的循环结构、Function Calling 的机制和工程上必须处理的问题。一、Agent 的组成 一个 Agent 通常包含四部分&#xff1a;…

作者头像 李华
网站建设 2026/11/4 11:58:27

APK Installer:在 Windows 上一次拖拽完成安卓应用安装

APK Installer&#xff1a;在 Windows 上一次拖拽完成安卓应用安装 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在电脑上跑安卓应用&#xff0c;通常要部署一套完整…

作者头像 李华
网站建设 2026/11/4 11:57:12

MPC-HC 2.3.4 封面变形修复指南:2分钟自查

MPC-HC 2.3.4 封面变形修复指南&#xff1a;2分钟自查 【免费下载链接】mpc-hc Media Player Classic 项目地址: https://gitcode.com/gh_mirrors/mp/mpc-hc MPC-HC 2.3.4 播放带内嵌专辑封面的音频时&#xff0c;封面会被拉得比例失调、边缘发虚&#xff0c;拖动播放器…

作者头像 李华
网站建设 2026/11/4 11:56:27

Driver Store Explorer:Windows 驱动清理与备份完整指南

Driver Store Explorer&#xff1a;Windows 驱动清理与备份完整指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer C 盘空间被占满却找不到原因&#xff0c;或者系统更新后显卡、网卡突…

作者头像 李华