最后更新:2026-04-17
JSON Web Token(JWT)是广泛使用的无状态认证方案。但错误实现会导致签名绕过、权限提升、信息泄露等严重安全问题。
| 维度 | 评级 |
|---|---|
| OWASP Top 10 | A07:2025 - Authentication Failures |
| CWE | CWE-327 / CWE-326 / CWE-200 |
| 严重程度 | 高危 |
| 攻击方式 | 说明 | 危害 |
|---|---|---|
alg:none 攻击 |
将算法置为 none,去除签名 | 完全绕过签名验证 |
| RS256 → HS256 混淆 | 将非对称算法改为对称,用公钥当密钥 | 伪造任意 Token |
| 弱密钥暴力破解 | 密钥过短,离线爆破 | 伪造任意 Token |
| JWT 信息泄露 | Payload 明文存储敏感信息 | 敏感数据泄露 |
未校验 exp/iss |
过期或无效 Token 仍被接受 | 权限持续滥用 |
// [VULNERABLE] 未限制允许的算法
@Component
public class JwtValidator {
public Claims parse(String token) {
// 危险:未指定允许的算法,攻击者可提交 alg:none 的 Token
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
}
// [SECURE] 明确指定允许的算法
@Component
public class SecureJwtValidator {
@Value("${jwt.secret}")
private String secret;
public Claims parse(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
// 安全:显式指定仅接受 HS256
.requireAlgorithm("HS256")
.build()
.parseClaimsJws(token)
.getBody();
}
}
// [VULNERABLE] 动态使用 Token Header 中声明的算法
public boolean verify(String token, PublicKey publicKey) {
// 危险:攻击者将 Header 中 alg 改为 HS256,用公钥作为 HMAC 密钥
String[] parts = token.split("\\.");
String headerJson = new String(Base64.getDecoder().decode(parts[0]));
String alg = parseAlg(headerJson); // 从 Token 中读取算法
return verifyWithAlg(token, alg, publicKey); // 使用攻击者指定的算法
}
// [SECURE] 服务端固定算法,不信任 Token Header 中的算法声明
@Component
public class RsaJwtService {
private final RSAPublicKey publicKey;
public Claims verify(String token) {
// 安全:固定使用 RS256,不读取 Token 中声明的算法
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("your-app")
.build();
DecodedJWT decoded = verifier.verify(token);
return extractClaims(decoded);
}
}
// [VULNERABLE] 密钥过短、硬编码、未校验过期时间
@Component
public class WeakJwtService {
// 危险:密钥过短(< 256 bit),可被离线爆破
private static final String SECRET = "secret";
public String generate(String userId) {
return Jwts.builder()
.setSubject(userId)
// 危险:未设置过期时间,Token 永不失效
.signWith(Keys.hmacShaKeyFor(SECRET.getBytes()))
.compact();
}
public Claims parse(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET.getBytes())
.build()
.parseClaimsJws(token)
// 危险:未校验 exp/iss/aud 等 Claims
.getBody();
}
}
// [SECURE] 强密钥、设置过期时间、完整校验 Claims
@Component
public class SecureJwtService {
// 从配置中心或环境变量读取,长度 >= 256 bit
@Value("${jwt.secret}")
private String secret;
private static final long EXPIRATION_MS = 3600_000L; // 1 小时
public String generate(String userId, String role) {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
SecretKey key = Keys.hmacShaKeyFor(keyBytes);
return Jwts.builder()
.setSubject(userId)
.claim("role", role)
.setIssuer("your-app")
.setAudience("your-client")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public Claims parse(String token) {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Jwts.parserBuilder()
.setSigningKey(keyBytes)
.requireIssuer("your-app") // 校验签发者
.requireAudience("your-client") // 校验受众
.build()
.parseClaimsJws(token) // 自动校验 exp
.getBody();
}
}
// [VULNERABLE] Payload 中存储敏感信息
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getId())
// 危险:Payload 仅 Base64 编码,任何人都可以解码
.claim("password", user.getPassword())
.claim("creditCard", user.getCreditCard())
.signWith(secretKey)
.compact();
}
// [SECURE] Payload 只存储必要的非敏感标识
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getId().toString())
// 安全:只存放角色/权限等非敏感元数据
.claim("roles", user.getRoles())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
SECRET 是否硬编码、是否设置 setExpirationalg 改为 none 并去掉签名,观察是否通过exp(过期)、iss(签发者)、aud(受众)