【架构实战】JWT认证:Token设计与安全边界
【架构实战】JWT认证Token设计与安全边界一、“无状态认证变成无法注销”2021年我们把Session认证升级成了JWT。初衷很简单微服务架构下Session共享太麻烦JWT无状态、不依赖Redis、天然适合分布式。上线后问题接踵而至用户修改密码后旧的JWT仍然有效因为JWT无法主动失效发现一个被盗用的Token但没有任何办法让它立即失效Token体积越来越大payload里塞了太多用户信息请求头超过8KBCTO质问“JWT不是最佳实践吗为什么我们的用户体验反而更差了”答案很简单JWT是无状态的但业务天然有状态。用无状态的方案解决有状态的问题必然要付出代价。二、JWT结构深入解析2.1 JWT三段式结构eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U │ │ │ │ Header │ Payload │ Signature │ Base64编码 │ Base64编码 │ HMAC签名 │ │ │ ▼ ▼ ▼ { alg: HS256, { HMACSHA256( typ: JWT sub: 1234567890, base64(header) . } name: 张三, base64(payload), iat: 1516239022, secret exp: 1516240022 ) }2.2 安全性分析// 常见错误使用弱密钥Stringsecret123456;// ❌ 弱密钥可暴力破解// 正确使用足够强度的密钥// HS256: 至少256位随机密钥Stringsecret2f7c8e9a1b3d4f5a6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8;// 或者使用非对称加密推荐// RS256: 私钥签名公钥验证KeyPairGeneratorkeyGenKeyPairGenerator.getInstance(RSA);keyGen.initialize(2048);KeyPairkeyPairkeyGen.generateKeyPair();// 签名用私钥StringtokenJwts.builder().setSubject(userId).signWith(keyPair.getPrivate(),SignatureAlgorithm.RS256).compact();// 验证用公钥微服务间分发公钥即可ClaimsclaimsJwts.parserBuilder().setSigningKey(keyPair.getPublic()).build().parseClaimsJws(token).getBody();三、JWT的设计实践3.1 Payload设计——小而精// 好的Payload设计精简原则{iss:https://auth.xxx.com,// 签发者sub:user_12345,// 用户唯一标识唯一必须字段aud:order-service,// 受众限制Token使用范围exp:1625097600,// 过期时间15分钟iat:1625096700,// 签发时间nbf:1625096700,// 生效时间jti:unique-token-uuid,// Token唯一IDrole:USER,// 角色尽量精简scope:read:profile// 权限范围}// 错误的Payload设计信息过载{sub:12345,username:zhangsan,email:zhangsanxxx.com,phone:13800138000,avatar:https://cdn.xxx.com/avatar/123.jpg,address:北京朝阳区xxx路xxx号,company:某某科技,department:研发部,position:高级工程师,permissions:[read,write,delete,admin,superadmin],metadata:{...}}// 问题Token体积过大每次请求都要传敏感信息暴露3.2 Token生成器实现ComponentpublicclassJwtTokenProvider{Value(${jwt.access-token-expiry:15})privateintaccessTokenExpiryMinutes;Value(${jwt.refresh-token-expiry:7})privateintrefreshTokenExpiryDays;privatefinalRSAPrivateKeyprivateKey;privatefinalRSAPublicKeypublicKey;privatefinalTokenBlacklistServiceblacklistService;/** * 生成Access Token短期15分钟 */publicStringgenerateAccessToken(UserPrincipaluser){InstantnowInstant.now();returnJwts.builder().setIssuer(https://auth.xxx.com).setSubject(user.getUserId().toString()).setAudience(api.xxx.com).setId(UUID.randomUUID().toString()).claim(role,user.getRole()).claim(scope,String.join( ,user.getPermissions())).setIssuedAt(Date.from(now)).setNotBefore(Date.from(now)).setExpiration(Date.from(now.plus(accessTokenExpiryMinutes,ChronoUnit.MINUTES))).signWith(privateKey,SignatureAlgorithm.RS256).compact();}/** * 生成Refresh Token长期7天可撤销 */publicStringgenerateRefreshToken(UserPrincipaluser){InstantnowInstant.now();StringtokenIdUUID.randomUUID().toString();// 将RefreshToken的ID存入Redis用于撤销验证redisTemplate.opsForValue().set(refresh_token:tokenId,user.getUserId().toString(),refreshTokenExpiryDays,TimeUnit.DAYS);returnJwts.builder().setSubject(user.getUserId().toString()).setId(tokenId).claim(type,refresh).setIssuedAt(Date.from(now)).setExpiration(Date.from(now.plus(refreshTokenExpiryDays,ChronoUnit.DAYS))).signWith(privateKey,SignatureAlgorithm.RS256).compact();}/** * 验证Token并提取用户信息 */publicUserPrincipalvalidateToken(Stringtoken){try{// 1. 验证签名和过期JwsClaimsjwsJwts.parserBuilder().setSigningKey(publicKey).requireIssuer(https://auth.xxx.com).requireAudience(api.xxx.com).build().parseClaimsJws(token);Claimsclaimsjws.getBody();StringjwtIdclaims.getId();// 2. 检查黑名单已注销的Tokenif(blacklistService.isBlacklisted(jwtId)){thrownewJwtException(Token已注销);}// 3. 构建用户主体returnUserPrincipal.builder().userId(Long.parseLong(claims.getSubject())).role(claims.get(role,String.class)).permissions(Arrays.asList(claims.get(scope,String.class).split( ))).build();}catch(JwtExceptione){thrownewAuthenticationException(Token验证失败: e.getMessage());}}}四、JWT的安全边界与补丁4.1 主动注销——Token黑名单ComponentpublicclassTokenBlacklistService{AutowiredprivateRedisTemplateString,StringredisTemplate;/** * 将Token加入黑名单 */publicvoidblacklistToken(StringjwtId,Dateexpiration){longttlexpiration.getTime()-System.currentTimeMillis();if(ttl0){// 存储JWT的唯一ID过期时间跟随原TokenredisTemplate.opsForValue().set(blacklist:jwtId,revoked,ttl,TimeUnit.MILLISECONDS);}}/** * 检查Token是否在黑名单中 */publicbooleanisBlacklisted(StringjwtId){returnBoolean.TRUE.equals(redisTemplate.hasKey(blacklist:jwtId));}}// 用户登出时PostMapping(/logout)publicvoidlogout(RequestHeader(Authorization)StringauthHeader){StringtokenauthHeader.substring(7);ClaimsclaimstokenProvider.parseClaims(token);// 将当前Access Token加入黑名单blacklistService.blacklistToken(claims.getId(),claims.getExpiration());// 删除Refresh TokenredisTemplate.delete(refresh_token:claims.getId());}4.2 Token刷新——旋转机制PostMapping(/refresh)publicTokenResponserefreshToken(RequestBodyRefreshTokenRequestrequest){// 1. 验证Refresh TokenClaimsclaimstokenProvider.parseClaims(request.getRefreshToken());StringtokenIdclaims.getId();// 2. 验证Refresh Token在Redis中存在未被撤销StringuserIdredisTemplate.opsForValue().get(refresh_token:tokenId);if(userIdnull){thrownewAuthenticationException(Refresh Token已失效);}// 3. 删除旧的Refresh Token旋转redisTemplate.delete(refresh_token:tokenId);// 4. 生成新的Token对UserPrincipaluseruserService.loadUserById(Long.parseLong(userId));StringnewAccessTokentokenProvider.generateAccessToken(user);StringnewRefreshTokentokenProvider.generateRefreshToken(user);returnnewTokenResponse(newAccessToken,newRefreshToken);}4.3 存储位置选择存储方式XSS安全CSRF安全适用场景注意事项localStorage不安全天然免疫不推荐XSS可直接读取sessionStorage不安全天然免疫不推荐同localStorageHttpOnly Cookie安全需防护Web应用推荐无法JS读取内存变量安全天然免疫SPA最优刷新页面需重新获取推荐方案Access Token 存内存Refresh Token 存 HttpOnly Secure SameSite Cookie。// Refresh Token Cookie配置ResponseCookierefreshCookieResponseCookie.from(refresh_token,refreshToken).httpOnly(true)// JS无法访问.secure(true)// 仅HTTPS.sameSite(Strict)// 防CSRF.path(/api/auth)// 仅认证接口携带.maxAge(7,ChronoUnit.DAYS).build();response.addHeader(HttpHeaders.SET_COOKIE,refreshCookie.toString());五、微服务中的JWT方案5.1 非对称密钥分发密钥管理方案 ┌─────────────┐ │ 认证服务 │ │ (持有私钥) │ │ 签发JWT │ └──────┬──────┘ │ ┌──────────┼──────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 订单服务 │ │ 商品服务 │ │ 用户服务 │ │ (只存公钥) │ │ (只存公钥) │ │ (只存公钥) │ └──────────┘ └──────────┘ └──────────┘ // 好处任何服务都可以验证Token但只有认证服务能签发Token // 微服务间不需要Redis共享Session真正实现了无状态5.2 服务间调用Token传递// 用户请求携带JWT// 服务A → 服务B透传JWT不重新签发ComponentpublicclassFeignClientInterceptorimplementsRequestInterceptor{Overridepublicvoidapply(RequestTemplatetemplate){// 从当前请求上下文获取原始JWTStringtokenRequestContextHolder.currentRequestAttributes().getAttribute(jwt_token,RequestAttributes.SCOPE_REQUEST);if(token!null){// 透传给下游服务template.header(Authorization,Bearer token);}}}六、JWT vs Session决策矩阵维度JWTSession扩展性天然分布式无需共享存储需要Redis等集中存储性能本地验证极快需要查询Redis注销需要黑名单补丁直接删除Session体积携带在请求头有大小限制仅传输SessionId跨域天然支持需要额外配置多设备登录需要额外逻辑天然支持数据权限Token内嵌过期前不变可实时更新七、总结JWT不是银弹它是一种权衡——用无法主动注销换取分布式无状态。理解了这一点才能做出正确的架构决策。核心安全边界Payload保持精简——用户ID 角色 权限不要塞用户资料Access Token 15分钟Refresh Token 7天 旋转机制非对称加密RS256优于对称加密HS256尤其在微服务场景HttpOnly Secure SameSite Cookie 是最安全的Token存储方式Token黑名单是主动注销的代价——用Redis承担这部分状态密钥定期轮换私钥泄露 整个认证体系崩溃选型建议微服务高并发 → JWT 非对称加密传统Web应用 → Session Redis对安全性要求极高 → JWT 短TTL 黑名单一句话JWT的本质是把状态从服务端转移到了Token本身。但业务天然有状态——用户要注销、管理员要封号、密码改了旧Token必须失效。接受这个现实用黑名单补丁兜底才是JWT的正确使用方式。个人观点仅供参考

相关新闻

VGGNet深度解析:3×3卷积核的设计优势与实践

VGGNet深度解析:3×3卷积核的设计优势与实践

1. VGGNet深度解析:33卷积核的革命性设计第一次看到VGGNet的架构图时,我被它惊人的简洁性震撼了——整个网络几乎全部由重复的33卷积堆叠而成。这种看似简单的设计背后,实则隐藏着牛津大学视觉几何组(Visual Geometry Group&#…

2026/7/14 17:05:38阅读更多 →
AMD Ryzen AI Phi-3-mini-4k-instruct_rai_1.7.1_hybrid:革命性AI模型优化方案

AMD Ryzen AI Phi-3-mini-4k-instruct_rai_1.7.1_hybrid:革命性AI模型优化方案

AMD Ryzen AI Phi-3-mini-4k-instruct_rai_1.7.1_hybrid:革命性AI模型优化方案 【免费下载链接】Phi-3-mini-4k-instruct_rai_1.7.1_hybrid 项目地址: https://ai.gitcode.com/hf_mirrors/amd/Phi-3-mini-4k-instruct_rai_1.7.1_hybrid AMD Ryzen AI Phi-3-…

2026/7/14 17:00:38阅读更多 →
MLX社区Gemma-4-E2B-it-8bit在创意产业的应用:10个图像描述与内容生成实用案例

MLX社区Gemma-4-E2B-it-8bit在创意产业的应用:10个图像描述与内容生成实用案例

MLX社区Gemma-4-E2B-it-8bit在创意产业的应用:10个图像描述与内容生成实用案例 【免费下载链接】gemma-4-e2b-it-8bit 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/gemma-4-e2b-it-8bit MLX社区Gemma-4-E2B-it-8bit是基于Google Gemma-4-E2…

2026/7/14 17:00:38阅读更多 →
【Springboot毕设全套源码+文档】基于springboot考研学生在线学习与交流系统的设计与实现(丰富项目+远程调试+讲解+定制)

【Springboot毕设全套源码+文档】基于springboot考研学生在线学习与交流系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/14 18:10:51阅读更多 →
开源OCR引擎Tesseract实战:从环境搭建到多语言识别

开源OCR引擎Tesseract实战:从环境搭建到多语言识别

1. Tesseract OCR引擎入门指南 第一次接触Tesseract时,我完全被这个开源OCR引擎的能力震撼到了。想象一下,你随手拍下一张报纸照片,几秒钟后就能得到可编辑的文字内容——这就是Tesseract带给我们的魔法。作为Google长期维护的开源项目&…

2026/7/14 18:10:51阅读更多 →
时间序列模型(四):ARIMA模型实战——从理论到Python代码实现

时间序列模型(四):ARIMA模型实战——从理论到Python代码实现

1. ARIMA模型基础概念 我第一次接触ARIMA模型是在分析电商平台的日销售额数据时。当时发现传统线性回归完全无法捕捉数据的波动规律,这才意识到时间序列数据的特殊性。ARIMA(Autoregressive Integrated Moving Average)作为时间序列分析的经典…

2026/7/14 18:10:51阅读更多 →
【Springboot毕设全套源码+文档】基于springboot+vue的零售商经营平台的设计与实现(丰富项目+远程调试+讲解+定制)

【Springboot毕设全套源码+文档】基于springboot+vue的零售商经营平台的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/14 18:10:51阅读更多 →
C++与Python混合编程实战:从PyBind11到高性能应用开发

C++与Python混合编程实战:从PyBind11到高性能应用开发

1. 项目概述:从“学无所用”到“学以致用”的编程认知跃迁我见过太多刚入行的开发者,尤其是从C或Python开始接触编程的朋友,常常陷入一个认知怪圈:他们花大量时间啃语法、刷算法题、研究各种“八股文”,但一到实际项目…

2026/7/14 18:10:51阅读更多 →
智能体与AI大模型的协同架构与应用实践

智能体与AI大模型的协同架构与应用实践

1. 智能体与AI大模型的协同本质 在当今AI技术快速发展的背景下,智能体(Agent)与AI大模型的协同已成为解决复杂任务的重要范式。这种协同不是简单的功能叠加,而是两种技术范式的深度融合与优势互补。 智能体本质上是一个具有自主决策能力的软件系统&…

2026/7/14 18:05:50阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/14 4:56:14阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/14 2:55:05阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/14 6:17:41阅读更多 →
【Cursor数据库安全红线】:自动执行SQL前必须校验的6项权限策略,金融级项目已强制落地

【Cursor数据库安全红线】:自动执行SQL前必须校验的6项权限策略,金融级项目已强制落地

更多请点击: https://intelliparadigm.com 第一章:Cursor数据库安全红线概览 Cursor 作为一款基于 AI 的智能编程助手,其本地数据库(SQLite 存储)承载着用户代码片段、会话历史、自定义规则及敏感上下文信息。理解其安…

2026/7/14 0:03:18阅读更多 →
【Notion AI写作避坑白皮书】:基于127份真实用户失败案例,总结6大致命误用陷阱

【Notion AI写作避坑白皮书】:基于127份真实用户失败案例,总结6大致命误用陷阱

更多请点击: https://codechina.net 第一章:Notion AI写作辅助的底层能力边界认知 Notion AI 并非通用大语言模型的直接封装,而是基于 Llama 系列与自研微调模型构建的轻量化推理服务,其输入上下文窗口严格限制在 8192 token&…

2026/7/14 0:03:18阅读更多 →
AI Agent数据越界行为如何被精准溯源?——基于GDPR/CCPA双合规的5层审计框架实战指南

AI Agent数据越界行为如何被精准溯源?——基于GDPR/CCPA双合规的5层审计框架实战指南

更多请点击: https://kaifayun.com 第一章:AI Agent数据越界行为的合规性挑战与溯源必要性 AI Agent在自主执行任务过程中,可能因提示注入、上下文污染或权限配置缺陷,无意或有意访问、缓存、传输受保护数据(如PII、G…

2026/7/14 0:03:18阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/14 15:07:30阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/14 4:45:36阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/14 2:42:17阅读更多 →