如何使用JJWT结合JWKS与RSA(RS256)验证客户发送的JWT
如何用JJWT结合JWKS验证RS256签名的JWT
我之前也遇到过类似的需求,下面是我整理的完整步骤和代码,应该能帮到你:
首先得明确RS256和HS256的核心区别:HS256用同一个对称密钥完成签名和验证,而RS256是非对称加密——用私钥签名JWT,用公钥验证签名。所以我们的核心任务就是从JWKS(JSON Web Key Set,密钥集合)中提取出对应的RSA公钥,再用它来验证JWT的合法性。
第一步:准备必要依赖
确保你的项目引入了JJWT的核心包,以及Jackson用来解析JWKS的JSON结构。如果用Maven,在pom.xml里添加这些依赖:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
第二步:从JWKS中提取对应RSA公钥
JWKS是一个包含多个JWK(单个密钥)的JSON集合,每个JWK都有kid(密钥ID)标识。我们需要先从JWT的Header里拿到kid,再在JWKS中找到匹配的密钥,最后转换为Java的RSAPublicKey对象。
下面是一个工具类方法,专门用来处理JWKS解析:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.security.Keys; import java.security.interfaces.RSAPublicKey; import java.util.Base64; public class JwksParser { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public static RSAPublicKey getMatchingRsaPublicKey(String jwksJson, String targetKid) throws Exception { JsonNode jwksRoot = OBJECT_MAPPER.readTree(jwksJson); JsonNode keysArray = jwksRoot.get("keys"); // 遍历JWKS中的所有密钥,找到与目标kid匹配的那一个 for (JsonNode keyNode : keysArray) { String currentKid = keyNode.get("kid").asText(); if (targetKid.equals(currentKid)) { // 提取RSA公钥的核心参数n(模数)和e(指数) String base64UrlN = keyNode.get("n").asText(); String base64UrlE = keyNode.get("e").asText(); // 注意:JWKS里的n和e是Base64URL编码,需要用专门的解码器 byte[] nBytes = Base64.getUrlDecoder().decode(base64UrlN); byte[] eBytes = Base64.getUrlDecoder().decode(base64UrlE); return (RSAPublicKey) Keys.createRsaPublicKey(nBytes, eBytes); } } throw new IllegalArgumentException("未找到与kid: " + targetKid + "匹配的公钥"); } }
第三步:完整的JWT验证流程
现在我们可以把流程串起来:先解析JWT的Header拿到kid,再从JWKS获取对应公钥,最后用JJWT验证签名和业务声明:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureException; import java.security.interfaces.RSAPublicKey; public class JwtValidator { public static void main(String[] args) { // 替换成客户给你的JWT字符串 String clientJwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE2OD..."; // 替换成客户提供的JWKS JSON内容 String jwksJson = "{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"168...\"}]}"; try { // 1. 先解析JWT的Header,提取kid(不需要验证签名,只解析头) String jwtKid = Jwts.parserBuilder() .build() .parseClaimsJwt(clientJwt.split("\\.")[0] + "." + clientJwt.split("\\.")[1] + ".") .getHeader() .get("kid", String.class); // 2. 从JWKS中获取匹配的RSA公钥 RSAPublicKey publicKey = JwksParser.getMatchingRsaPublicKey(jwksJson, jwtKid); // 3. 正式验证JWT的签名和业务声明 Claims claims = Jwts.parserBuilder() .setSigningKey(publicKey) // 设置验证签名用的公钥 .requireIssuer("https://your-client-issuer.com") // 可选:验证签发者,根据实际需求添加 .requireAudience("your-app-audience") // 可选:验证受众 .build() .parseClaimsJws(clientJwt) .getBody(); // 验证成功,处理业务逻辑 System.out.println("JWT验证通过,用户ID:" + claims.getSubject()); // 可以从claims中提取其他自定义字段,比如claims.get("role", String.class) } catch (ExpiredJwtException e) { System.err.println("JWT已过期:" + e.getMessage()); } catch (SignatureException e) { System.err.println("JWT签名无效:" + e.getMessage()); } catch (MalformedJwtException e) { System.err.println("JWT格式错误:" + e.getMessage()); } catch (Exception e) { System.err.println("JWT验证失败:" + e.getMessage()); } } }
关键注意点
- 必须匹配kid:JWKS可能包含多个密钥(比如密钥轮换场景),JWT头里的
kid是用来指定签名密钥的标识,验证时一定要用对应的公钥,否则签名验证会失败。 - Base64URL编码:JWKS中的
n和e是Base64URL编码(和普通Base64的区别是替换了+/-、//_,去掉了末尾的=),所以必须用Base64.getUrlDecoder()解码。 - 自定义验证规则:除了签名,你可以根据业务需求添加更多验证项,比如过期时间、签发者、受众等,JJWT会自动帮你检查这些声明是否符合预期。
内容的提问来源于stack exchange,提问作者javaTry
相关产品推荐
相关产品推荐

