You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:26:14