这是一个或许对你有用的社群 一对一交流/面试小册/简历优化/求职解惑欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料《项目实战视频》从书中学往事中“练”《互联网高频面试题》面朝简历学习春暖花开《架构 x 系统设计》摧枯拉朽掌控面试高频场景题《精进 Java 学习指南》系统学习互联网主流技术栈《必读 Java 源码专栏》知其然知其所以然这是一个或许对你有用的开源项目国产Star破10w的开源项目前端包括管理后台、微信小程序后端支持单体、微服务架构RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能多模块https://gitee.com/zhijiantianya/ruoyi-vue-pro微服务https://gitee.com/zhijiantianya/yudao-cloud视频教程https://doc.iocoder.cn【国内首批】支持 JDK17/21SpringBoot3、JDK8/11Spring Boot2双版本先把结论摆桌面JWT 不是不好是用错了地方JWT 是什么30 秒看懂登录流程JWT 做会话的 4 个真实约束破解撤销难题JWT Redis 短黑名单最省内存选型决策什么场景用 JWT什么场景用 Session一句话收口先把结论摆桌面JWT 不是不好是用错了地方网上写 JWT 的文章要么吹爆无状态、性能好要么一棒打死千万别用。这俩都偏激。真实的结论是JWT 是一项设计精良的技术但它的最佳战场不是用户登录会话——它本来是为两端之间传递一次性授权声明设计的。不过配合一些工程手段比如 Redis 短黑名单JWT 也能在登录会话场景里跑得很稳。本文要做的就是把这条工程路径讲清楚——让你不再纠结用不用 JWT而是知道怎么用得对。基于 Spring Boot MyBatis Plus Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/ruoyi-vue-pro视频教程https://doc.iocoder.cn/video/JWT 是什么30 秒看懂登录流程JWT 全称 JSON Web Token。本质就是一段 JSON 数据用密钥签名后让接收方能验证它来自你认识的人。官网https://jwt.io/一个 JWT 长这样三段式header.payload.signature典型登录流程你在网站登录成功服务器签发一个 JWT 给你JWT 里塞着你的身份信息用户名、角色、权限……之后你每次请求都把这个 JWT 带上服务器只验签名就能信任 JWT 里的信息——不需要查库验证通过请求放行这个不查库是 JWT 最大的卖点——也是后面 4 个约束的根源。基于 Spring Cloud Alibaba Gateway Nacos RocketMQ Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/yudao-cloud视频教程https://doc.iocoder.cn/video/JWT 做会话的 4 个真实约束会话管理要做的事情记录用户身份、权限校验、注销/封禁。下面看 JWT 在每一项上的真实表现。约束 1体积膨胀每次请求都带上整个 token存一个用户 IDxiaoucookie 只需要 5 字节。塞进 JWT 里就膨胀大约 51 倍每次请求都带一遍——会话场景下用户请求频次高这部分带宽不可忽略。修法payload 只塞必要字段——uidrole够了别把整个用户对象都塞进去根本上是减小体积配 refresh token 短 access token——access token 设 15minrefresh token 走更安全通道HttpOnly Cookie / 单独刷新接口降低单次暴露面极敏感场景退回服务端会话——网关用 JWT 鉴权后换发短期不透传的服务端票据敏感操作走服务端票据⚠️别指望 HTTP 层 gzip——gzip 压缩的是 response bodyAuthorization Header 和 Cookie 不在压缩范围。HTTP/2 的 HPACK 会做 header 压缩但效果有限节流的根本还是减小 token 自身大小约束 2签名只是 hygiene不是亮点JWT 加密签名让接收方能验证 token 真伪——但几乎每一个现代 web 框架在使用普通会话 cookie 时都自动加密签名。Spring Session、Express、Django……一个比一个完善。实际上绝大多数项目的 JWT 又被放在会话 cookie 里——这意味着同一份数据被签了两次。这不算缺点只是它没你想的那么独特。约束 3撤销难注销不算真正注销最关键这是 JWT 在会话场景下最需要解决的问题。JWT 是自包含的——服务器没办法主动撤销一个还没到期的 token。后果是注销不算真注销用户点了退出但泄露出去的 JWT 还能用——直到 token 过期权限变更不立即生效管理员降级为普通用户他的 JWT 里写的还是 admin继续保留管理员权限直到 token 过期场景Session Cookie原生 JWT注销立即失效✅ 删 session 即可❌ 必须等到期权限变更生效✅ 下次请求查库即更新❌ token 内容已固化强制下线✅ session 后端控❌ 没法强制修法下一节的Redis 短黑名单专门解这个问题——用 1% 的 Session 内存换回 90% 的撤销能力。约束 4默认明文要靠 HTTPS 兜底JWT 默认只签名不加密——payload 是 base64 编码的谁拿到都能解开看里面的内容。这不是加密是编码。如果传输链路被中间人嗅探HTTPS 配置疏漏、内网代理被攻破等JWT 等于明文凭证。修法必须走 HTTPS——这是任何 web 应用的底线不仅是 JWT 的事payload 不放敏感字段——身份证、手机号、地址这种绝对不要塞进 JWT真要加密——用 JWEJSON Web Encryption但工程成本会上升破解撤销难题JWT Redis 短黑名单最省内存撤销难是 JWT 在会话场景的核心痛点——主流解法是维护一份失效 token 黑名单。最省内存的实现方案Redis 黑名单 短 TTL按 token 剩余有效期设核心思想黑名单不存所有失效的 token——只存还没到期但已失效的。一旦 token 自然过期自动从黑名单消失。Service publicclass JwtBlacklistService { Autowired private StringRedisTemplate redisTemplate; /** * 用户主动登出 / 强制下线时调用 * param token JWT token * param expiresAt token 自然过期时间戳秒 */ public void revoke(String token, long expiresAt) { long ttl expiresAt - System.currentTimeMillis() / 1000; if (ttl 0) return; // 已经过期了没必要拉黑 // 用 token 的 jtiJWT ID做 key避免存全 token 占内存 String jti JwtUtil.parseJti(token); redisTemplate.opsForValue().set( jwt:blacklist: jti, 1, ttl, TimeUnit.SECONDS // ⭐ TTL 设为 token 剩余有效期 ); } public boolean isRevoked(String token) { String jti JwtUtil.parseJti(token); return Boolean.TRUE.equals( redisTemplate.hasKey(jwt:blacklist: jti)); } }每次请求验签后多查一次 Redispublic boolean validateToken(String token) { if (!JwtUtil.verifySignature(token)) return false; if (blacklistService.isRevoked(token)) return false; // ⭐ 新增黑名单检查 return true; }为什么这套方案省内存方案Redis 内存占用说明完整 Session 存 Redis100%每个用户存完整身份信息500BJWT 黑名单本方案~5%只存被拉黑的 jti——绝大多数 token 不在名单里不维护黑名单裸 JWT0但失去撤销能力省内存关键三点只存 jtiJWT ID——一个短字符串不是整个 tokenTTL token 剩余有效期——token 自然过期后黑名单也跟着清永远不膨胀黑名单只占小部分——99% 的 token 没被撤销根本不存 Redis进阶批量强制下线全量令牌作废特殊场景密码泄漏需要让某用户所有现有 token 都失效。这时按 jti 拉黑不够——你不知道用户签发过多少 token。做法黑名单里存用户 ID 的 token 失效起点时间验签时比对 token 签发时间// 把该用户在 X 时间之前签发的所有 token 视为已撤销 redisTemplate.opsForValue().set( jwt:user-revoked: userId, String.valueOf(System.currentTimeMillis() / 1000), 7L * 24 * 3600, TimeUnit.SECONDS // 7 天 TTL超过这时间的旧 token 早就过期了 ); // 验签时检查 public boolean validateToken(String token) { if (!JwtUtil.verifySignature(token)) returnfalse; long iat JwtUtil.parseIat(token); // token 签发时间 String userId JwtUtil.parseSubject(token); String revokeBefore redisTemplate.opsForValue().get(jwt:user-revoked: userId); if (revokeBefore ! null iat Long.parseLong(revokeBefore)) { returnfalse; // 早于失效起点撤销 } returntrue; }选型决策什么场景用 JWT什么场景用 Session场景推荐原因传统单体 Web 项目Session Cookie框架内置、撤销简单、零工程成本跨域/跨服务的 SSOJWT各服务无状态校验不用共享 session 存储移动端 / SPAJWT Redis 黑名单移动端 cookie 不便JWT 更灵活黑名单解决撤销微服务体系JWT 网关统一验签网关层解析、下游服务无状态长会话30 天JWT Refresh Tokenaccess token 短15minrefresh token 长30 天 Redis 黑名单OAuth2 access tokenJWT这就是 JWT 的亲生场景——一次性授权金融交易、合规要求高Session Cookie 服务端会话撤销/审计/合规要求 → 服务端可控说白了要简单、单体应用、不用跨域Session Cookie 永远没错跨域、跨服务、要 SSOJWT 是合适的——别忘了配 Redis 黑名单解决撤销金融/医疗/合规场景传统会话 服务端审计——别为无状态牺牲可控性一句话收口JWT 不是糟糕的技术它只是被很多人用在了它没设计好的场景——单体应用的会话管理。真正的工程姿势是明确场景——单体跨服务OAuthSSO不同场景选不同方案如果用 JWT 做会话——配 Redis 短黑名单用 5% 的 Redis 内存换回撤销能力payload 只塞必要字段、必走 HTTPS、敏感数据不入 JWT——三条底线记住好工程师不是用什么技术的问题而是知道什么时候用什么、用了之后补什么短板。参考资料系统化分析https://research.securitum.com/jwt-json-web-token-security/攻击模式汇总https://www.freebuf.com/articles/web/375465.html欢迎加入我的知识星球全面提升技术能力。 加入方式“长按”或“扫描”下方二维码噢星球的内容包括项目实战、面试招聘、源码解析、学习路线。文章有帮助的话在看转发吧。 谢谢支持哟 (*^__^*