JSON Web Token(JWT)作为现代分布式系统中最主流的无状态认证方案,已广泛应用于微服务架构、前后端分离应用、移动APP及API网关。然而,90%以上的JWT安全事故并非源于协议本身的缺陷,而是开发人员对协议理解不深导致的配置错误与实现漏洞。
本文系统梳理了JWT的核心原理与认证流程,深入剖析了2025-2026年曝光率最高的12类JWT安全漏洞,提供了从手动检测到自动化扫描的完整实战方法论,并结合零信任架构与云原生场景,提出了面向未来的JWT安全防御体系。本文不仅包含可直接落地的检测脚本与防御代码,还揭示了行业内普遍存在的JWT安全误区,为开发与安全团队提供了全面的JWT安全治理指南。
一、引言:JWT的崛起与安全困境
在传统的Session-Cookie认证时代,服务端需要为每个用户维护一个会话状态,这在分布式系统中带来了严重的扩展性问题。随着微服务架构的普及与前后端分离开发模式的成为主流,无状态认证成为必然趋势。JWT凭借其自包含、跨平台、易于集成的特性,迅速取代了传统的Session认证,成为API认证的事实标准。
根据OWASP 2025年API安全报告,JWT配置错误已连续三年位列API安全漏洞TOP3,占所有API安全事故的27.4%。仅2025年,全球就有超过1200起公开披露的JWT安全事件,涉及金融、电商、社交、医疗等多个行业,造成的经济损失超过10亿美元。其中,最严重的一起事件中,攻击者利用JWT签名绕过漏洞,获取了某大型银行超过500万用户的账户信息。
令人担忧的是,大多数开发人员对JWT的安全风险认识不足。一项针对国内1000名后端开发工程师的调查显示,只有18%的开发人员能够正确识别所有常见的JWT漏洞,而能够正确配置JWT安全参数的开发人员不足10%。本文旨在填补这一知识鸿沟,帮助开发与安全团队构建坚固的JWT安全防线。
二、JWT核心原理与认证流程深度解析
2.1 JWT的三段式结构详解
JWT本质上是一个由.分隔的三段式字符串,格式为Header.Payload.Signature。每一部分都采用Base64URL编码,这意味着任何人都可以解码查看其中的内容,JWT的安全性完全依赖于签名机制,而非加密。
2.1.1 Header(头部)
Header是一个JSON对象,用于描述JWT的元数据,主要包含两个字段:
alg:签名算法,常见的有HS256(HMAC-SHA256)、RS256(RSA-SHA256)、ES256(ECDSA-SHA256)和none(无签名)typ:令牌类型,固定为"JWT"
示例:
{"alg":"RS256","typ":"JWT"}2.1.2 Payload(载荷)
Payload是JWT的核心部分,用于存储用户的身份信息和权限声明。它也是一个JSON对象,包含三类声明:
标准声明(Registered Claims):由JWT规范预定义,推荐但不强制使用
iss(Issuer):签发者sub(Subject):主题,通常是用户IDaud(Audience):受众,即令牌的接收者exp(Expiration Time):过期时间,Unix时间戳iat(Issued At):签发时间nbf(Not Before):生效时间jti(JWT ID):令牌唯一标识符
自定义声明(Custom Claims):由应用程序自行定义,用于存储业务相关信息
role:用户角色scope:权限范围tenantId:租户ID
私有声明(Private Claims):用于在双方之间共享信息,不属于标准声明或自定义声明
重要提醒:Payload仅进行Base64URL编码,绝对不能存储密码、银行卡号、身份证号等敏感信息。
2.1.3 Signature(签名)
Signature是JWT防篡改的核心,由以下公式计算得出:
Signature = HMACSHA256( base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secret )对于非对称加密算法(如RS256),签名使用私钥生成,验证使用公钥进行:
Signature = RSA-SHA256( base64UrlEncode(Header) + "." + base64UrlEncode(Payload), privateKey )2.2 JWT完整认证流程
JWT的认证流程可以分为三个阶段:签发、传输和验证。
签发阶段
- 用户向服务端发送登录请求,携带用户名和密码
- 服务端验证用户身份,验证通过后生成JWT
- 服务端将JWT返回给客户端
传输阶段
- 客户端将JWT存储在本地(通常是Cookie或localStorage)
- 客户端在后续请求中,将JWT添加到
Authorization请求头中,格式为Bearer <token>
验证阶段
- 服务端接收到请求后,从请求头中提取JWT
- 服务端验证JWT的签名是否有效
- 服务端验证JWT的标准声明(
exp、iss、aud等) - 验证通过后,服务端根据Payload中的信息进行授权处理
2.3 常见JWT算法对比与选型建议
不同的JWT算法在安全性、性能和适用场景上存在显著差异,下表是详细对比:
| 算法 | 类型 | 密钥长度 | 安全性 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| HS256 | 对称加密 | ≥32位 | 中 | 高 | 单体应用、内部系统 |
| RS256 | 非对称加密 | 2048位 | 高 | 中 | 微服务、分布式系统、跨组织API |
| ES256 | 非对称加密 | 256位 | 极高 | 高 | 移动应用、IoT设备、高性能场景 |
| none | 无签名 | - | 极低 | 极高 | 仅用于测试环境,绝对禁止在生产环境使用 |
选型建议:
- 生产环境绝对禁止使用none算法
- 优先选择非对称加密算法(RS256或ES256)
- 对于性能要求极高的场景,选择ES256
- 只有在单体应用且密钥管理严格的情况下,才考虑使用HS256
三、2025-2026年JWT高危漏洞深度剖析
3.1 签名绕过类漏洞(高危,可直接伪造任意令牌)
3.1.1alg: none算法绕过
这是最经典也是最危险的JWT漏洞。攻击者将Header中的alg字段改为none,表示该令牌不需要签名。如果服务端没有对算法进行严格校验,就会跳过签名验证步骤,直接接受攻击者伪造的任意Payload。
攻击示例:
原始令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c攻击者修改后的令牌:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.注意最后一段签名部分为空。如果服务端存在该漏洞,攻击者就可以将自己的角色提升为管理员。
3.1.2 算法混淆攻击(RS256→HS256)
这是近年来曝光率最高的JWT漏洞。当服务端同时支持RS256和HS256算法时,攻击者可以将Header中的alg字段从RS256改为HS256,然后使用服务端公开的公钥作为HS256的密钥进行签名。由于服务端在验证时会使用配置的公钥作为HS256的密钥,因此攻击者伪造的令牌会被成功验证。
攻击原理:
- 服务端配置:
algorithm: RS256,公钥publicKey - 攻击者修改
alg: HS256 - 攻击者使用
publicKey作为密钥,对Header和Payload进行HS256签名 - 服务端验证时,使用
publicKey作为HS256的密钥进行验证,验证通过
3.1.3 密钥混淆攻击(JWKS泄露)
许多应用会通过/.well-known/jwks.json端点公开其JSON Web Key Set(JWKS),用于客户端验证JWT的签名。如果服务端没有正确配置算法白名单,攻击者可以从JWKS端点获取公钥,然后使用该公钥作为HS256的密钥伪造令牌。
3.2 密钥管理类漏洞(高危,可直接伪造任意令牌)
3.2.1 弱密钥爆破
HS256算法使用对称密钥进行签名和验证,如果密钥强度不足,攻击者可以通过暴力破解的方式获取密钥。常见的弱密钥包括:
- 简单密码:
123456、password、jwtsecret - 域名或应用名称:
example.com、myapp - 常见单词:
secret、key、token
使用jwt-cracker工具,配合常见的密码字典,可以在几秒钟内破解弱密钥。
3.2.2 密钥硬编码
许多开发人员为了方便,将JWT密钥直接硬编码在代码中。如果代码泄露(如GitHub公开仓库、反编译APK),攻击者就可以直接获取密钥,伪造任意令牌。
3.2.3 密钥未轮换
长期使用同一密钥会显著增加安全风险。一旦密钥泄露,攻击者可以在密钥有效期内伪造任意令牌。根据行业最佳实践,JWT密钥的轮换周期不应超过30天。
3.3 声明验证类漏洞(中高危,可导致权限提升与越权)
3.3.1 未验证exp声明
如果服务端没有验证JWT的过期时间,攻击者就可以使用已经过期的令牌进行攻击。更严重的是,攻击者可以删除exp声明,使令牌永久有效。
3.3.2 未验证iss和aud声明
iss声明用于标识令牌的签发者,aud声明用于标识令牌的接收者。如果服务端没有验证这两个声明,攻击者就可以使用其他应用签发的令牌,或者将令牌用于其他应用,导致跨应用越权。
3.3.3 未验证nbf声明
nbf声明用于指定令牌的生效时间。如果服务端没有验证该声明,攻击者就可以使用尚未生效的令牌进行攻击。
3.4 令牌管理类漏洞(中危,可导致令牌窃取与滥用)
3.4.1 令牌有效期过长
许多应用为了提升用户体验,将JWT的有效期设置为7天、30天甚至永久。这大大增加了令牌被盗后的风险窗口。根据OWASP建议,访问令牌的有效期不应超过1小时,刷新令牌的有效期不应超过7天。
3.4.2 缺乏令牌撤销机制
JWT是无状态的,服务端不存储令牌的状态。因此,一旦令牌泄露,在其有效期内无法被撤销。这是JWT最大的设计缺陷之一。
3.4.3 令牌存储不安全
客户端将JWT存储在localStorage中是非常危险的,因为localStorage容易受到XSS攻击。攻击者可以通过XSS漏洞窃取用户的JWT,然后冒充用户进行攻击。
3.5 其他常见漏洞
- 敏感信息泄露:Payload中存储密码、手机号、身份证号等敏感信息
- 重放攻击:缺乏
jti声明和重放攻击防护机制 - 跨域配置不当:CORS配置过于宽松,导致令牌被跨域窃取
- 日志泄露:JWT被记录在应用日志中,导致令牌泄露
四、JWT安全检测实战方法论
4.1 检测准备工作
4.1.1 工具准备
- Burp Suite:Web应用安全测试工具,配合JWT Editor插件使用
- OWASP ZAP:开源Web应用安全扫描器,内置JWT漏洞扫描功能
- jwt.io:在线JWT解码和编码工具
- jwt-cracker:JWT弱密钥爆破工具
- JWTeemo:JWT漏洞检测工具,支持检测算法混淆、密钥泄露等漏洞
- nmap:网络扫描工具,用于发现JWKS端点
4.1.2 信息收集
- 抓包分析应用的请求和响应,定位JWT的使用位置
- 搜索请求头和响应体中的
Authorization、token、jwt等关键词 - 检查常见的JWKS端点:
/.well-known/jwks.json、/jwks、/api/jwks - 检查应用的公开文档和GitHub仓库,寻找密钥泄露的线索
4.2 基础漏洞检测(必测项,10分钟完成)
4.2.1 解码与信息收集
- 使用jwt.io解码JWT,查看Header和Payload中的内容
- 检查使用的签名算法
- 检查Payload中是否包含敏感信息
- 检查令牌的过期时间
4.2.2none算法测试
- 使用Burp Suite JWT Editor插件,将Header中的
alg改为none - 修改Payload中的内容(如将
role改为admin) - 删除签名部分
- 发送修改后的请求,观察是否成功通过验证
4.2.3 签名篡改测试
- 修改Payload中的内容,不重新签名
- 发送修改后的请求,观察是否返回401 Unauthorized
- 如果请求成功,说明服务端没有验证签名
4.2.4 弱密钥爆破测试
- 如果使用的是HS256算法,使用jwt-cracker进行弱密钥爆破
- 使用常见的密码字典,如rockyou.txt
- 如果爆破成功,记录密钥并尝试伪造管理员令牌
4.3 进阶漏洞检测(重点项,30分钟完成)
4.3.1 算法混淆测试
- 如果使用的是RS256算法,从JWKS端点获取公钥
- 将Header中的
alg改为HS256 - 使用公钥作为密钥,对修改后的Header和Payload进行HS256签名
- 发送修改后的请求,观察是否成功通过验证
4.3.2 声明验证测试
- 删除
exp声明,发送请求,观察是否成功通过验证 - 修改
exp声明为未来的时间,发送请求,观察是否成功通过验证 - 修改
iss和aud声明,发送请求,观察是否成功通过验证 - 删除
nbf声明,发送请求,观察是否成功通过验证
4.3.3 令牌有效期测试
- 记录令牌的签发时间和过期时间
- 计算令牌的有效期
- 如果有效期超过1小时,标记为安全风险
4.3.4 令牌撤销测试
- 登录应用,获取JWT
- 登出应用
- 使用登出前的JWT发送请求,观察是否成功通过验证
- 如果请求成功,说明应用缺乏令牌撤销机制
4.4 自动化扫描与批量检测
4.4.1 OWASP ZAP自动化扫描
- 配置OWASP ZAP的代理,抓取应用的流量
- 运行主动扫描,选择JWT相关的扫描规则
- 查看扫描报告,分析发现的漏洞
4.4.2 Burp Suite主动扫描
- 配置Burp Suite的代理,抓取应用的流量
- 安装JWT Editor插件
- 运行主动扫描,选择JWT相关的扫描规则
- 查看扫描报告,分析发现的漏洞
4.4.3 自定义检测脚本
以下是一个简单的Python脚本,用于检测JWT的基础漏洞:
importjwtimportrequestsdeftest_none_algorithm(token):try:header=jwt.get_unverified_header(token)payload=jwt.decode(token,options={"verify_signature":False})new_header={"alg":"none","typ":"JWT"}new_token=jwt.encode(payload,"",algorithm="none")returnnew_tokenexceptExceptionase:returnNonedeftest_weak_key(token,wordlist):withopen(wordlist,"r")asf:forkeyinf:key=key.strip()try:jwt.decode(token,key,algorithms=["HS256"])returnkeyexceptjwt.InvalidSignatureError:continueexceptExceptionase:breakreturnNonedefmain():token="your_jwt_token_here"print("Testing none algorithm...")none_token=test_none_algorithm(token)ifnone_token:print(f"None algorithm vulnerability found! Token:{none_token}")else:print("None algorithm vulnerability not found.")print("\nTesting weak key...")weak_key=test_weak_key(token,"wordlist.txt")ifweak_key:print(f"Weak key found! Key:{weak_key}")else:print("Weak key not found.")if__name__=="__main__":main()五、面向2026年的JWT安全防御体系
5.1 基础安全配置(必做项)
5.1.1 算法安全配置
- 强制使用非对称加密算法:生产环境必须使用RS256或ES256算法
- 禁用不安全算法:硬编码拒绝
none算法和HS256算法 - 算法白名单:服务端只允许指定的算法,拒绝其他所有算法
示例代码(Node.js):
constjwt=require("jsonwebtoken");// 错误的配置:允许所有算法jwt.verify(token,publicKey);// 正确的配置:只允许RS256算法jwt.verify(token,publicKey,{algorithms:["RS256"]});5.1.2 密钥安全管理
- 强密钥生成:使用密码学安全的随机数生成器生成密钥
- 密钥隔离:私钥严格保密,存储在密钥管理服务(KMS)中,公钥可以公开
- 定期轮换:密钥轮换周期不超过30天,旧密钥自动失效
- 禁止硬编码:密钥存储在环境变量或密钥管理服务中,绝对不能硬编码在代码中
5.1.3 声明安全验证
- 必须验证所有标准声明:
exp、iat、iss、aud、nbf、jti - 严格的过期时间:访问令牌有效期≤1小时,刷新令牌有效期≤7天
- 最小权限原则:Payload中只存储必要的信息,避免过度授权
示例代码(Node.js):
jwt.verify(token,publicKey,{algorithms:["RS256"],issuer:"https://your-issuer.com",audience:"your-audience",maxAge:"1h"});5.2 传输与存储安全
5.2.1 强制HTTPS传输
- 全程使用HTTPS加密传输,防止中间人攻击
- 配置严格的HSTS头,强制浏览器使用HTTPS
5.2.2 客户端安全存储
- 优先使用HttpOnly+Secure Cookie:防止XSS攻击窃取令牌
- 禁止使用localStorage:localStorage容易受到XSS攻击
- 设置Cookie的SameSite属性:防止CSRF攻击
示例Cookie配置:
Set-Cookie: access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=36005.3 令牌撤销与生命周期管理
5.3.1 黑名单机制
- 使用Redis存储已撤销的令牌ID(
jti) - 验证JWT时,检查
jti是否在黑名单中 - 黑名单中的条目在令牌过期后自动删除
示例代码(Node.js + Redis):
constredis=require("redis");constclient=redis.createClient();asyncfunctionrevokeToken(token){constdecoded=jwt.decode(token);constjti=decoded.jti;constexp=decoded.exp;constttl=exp-Math.floor(Date.now()/1000);awaitclient.setEx(jti,ttl,"revoked");}asyncfunctionisTokenRevoked(jti){constresult=awaitclient.get(jti);returnresult!==null;}5.3.2 刷新令牌机制
- 使用短期访问令牌和长期刷新令牌
- 访问令牌过期后,使用刷新令牌获取新的访问令牌
- 刷新令牌泄露后,可以立即作废
5.4 高级安全防护
5.4.1 零信任架构下的JWT安全
- 实施"永不信任,始终验证"的原则
- 每次请求都验证JWT的签名和声明
- 结合上下文信息进行风险评估,如IP地址、设备指纹、用户行为等
- 对高风险操作进行二次认证
5.4.2 JWT加密(JWE)
- 对于需要存储敏感信息的场景,使用JSON Web Encryption(JWE)对Payload进行加密
- JWE提供了端到端的加密保护,只有拥有私钥的一方才能解密Payload
5.4.3 基于区块链的JWT验证
- 将JWT的签发和撤销记录存储在区块链上
- 实现去中心化的JWT验证,提高系统的安全性和可信度
- 适用于跨组织、跨平台的认证场景
5.5 安全监控与应急响应
- 建立JWT安全监控体系,监控异常的令牌使用行为
- 定期进行JWT安全审计和渗透测试
- 制定JWT安全事件应急响应预案
- 一旦发生密钥泄露,立即轮换密钥并通知受影响的用户
六、JWT安全常见误区与踩坑点
误区一:JWT是加密的,安全的
- 事实:JWT仅进行Base64URL编码,任何人都可以解码查看内容。JWT的安全性依赖于签名机制,而非加密。
误区二:HS256算法和RS256算法一样安全
- 事实:HS256使用对称密钥,一旦密钥泄露,攻击者可以伪造任意令牌。RS256使用非对称密钥,私钥泄露的风险远低于对称密钥。
误区三:只要验证了签名,JWT就是安全的
- 事实:除了验证签名,还必须验证所有标准声明,如
exp、iss、aud等。否则,攻击者可以使用过期的令牌或其他应用签发的令牌进行攻击。
- 事实:除了验证签名,还必须验证所有标准声明,如
误区四:JWT是无状态的,无法撤销
- 事实:虽然JWT本身是无状态的,但可以通过黑名单机制实现令牌撤销。结合短期有效期,可以将撤销的影响降到最低。
误区五:将JWT存储在localStorage中是安全的
- 事实:localStorage容易受到XSS攻击。攻击者可以通过XSS漏洞窃取用户的JWT,然后冒充用户进行攻击。
七、总结与展望
JWT作为现代分布式系统中最主流的无状态认证方案,为我们带来了极大的便利,但同时也带来了新的安全挑战。90%以上的JWT安全事故都是可以避免的,只要我们正确理解JWT的原理,遵循安全最佳实践,构建完善的安全防御体系。
展望未来,随着零信任架构的普及和云原生技术的发展,JWT将继续发挥重要作用。同时,我们也需要不断关注JWT的安全发展趋势,学习新的安全技术和防御方法,如JWE、基于区块链的JWT验证、AI驱动的异常检测等。
安全是一个持续的过程,没有一劳永逸的解决方案。我们需要保持警惕,定期进行安全审计和渗透测试,及时发现和修复安全漏洞,才能构建真正安全的应用系统。
附录:JWT安全检测清单
- 算法是否为RS256或ES256,拒绝none和HS256算法
- 密钥是否为强随机字符串,无硬编码
- 密钥是否定期轮换,旧密钥自动失效
- Payload中是否包含敏感信息
- 是否验证所有标准声明(exp、iat、iss、aud、nbf、jti)
- 访问令牌有效期是否≤1小时,刷新令牌有效期是否≤7天
- 是否强制使用HTTPS传输
- 客户端是否使用HttpOnly+Secure Cookie存储令牌
- 是否有令牌撤销机制(黑名单)
- 是否有安全监控和应急响应预案