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

Keycloak 21.0.0调用外部JWK API验签时提示公钥未找到

Keycloak 21.0.0外部JWK验签失败(公钥未找到)排查方案

问题背景

在Keycloak 21.0.0自定义认证流程中,调用外部JWK API验签时出现公钥未找到的错误,日志提示:

WARN [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider]PublicKey wasn't found in the storage. Requested kid:'sig-2024-10-07-DB'. Available kids: '[]'
WARN [org.keycloak.services]KC-SERVICES0097: Invalid request: java.lang.RuntimeException: Failed to verify signature on 'request' object

核心问题排查与修复

1. 验签逻辑致命错误:直接使用JWT Header的Base64编码作为kid

在verifySignature方法中,代码错误地将jwtParts[0](JWT Header的Base64URL编码串)当作kid去查找JWK,完全不符合JWT规范。正确做法是解码Header部分,提取其中的kid字段。

错误代码片段:

JWK jwk = euroInfoJwkSet.getKeyByKeyId(jwtParts[0]);

修复后的代码片段:

// 解码JWT Header,提取kid
String headerJson = new String(Base64.getUrlDecoder().decode(jwtParts[0]), StandardCharsets.UTF_8);
JsonObject header = JsonParser.parseString(headerJson).getAsJsonObject();
String kid = header.get("kid").getAsString();
JWK jwk = euroInfoJwkSet.getKeyByKeyId(kid);

2. 检查外部JWK API返回内容

  • 直接调用https://request/jwk_request.cgi?client_id=5g4h4r8r,确认返回的JWK Set中是否包含日志提到的sig-2024-10-07-DB
  • 验证返回JSON格式是否符合JWK Set标准(顶级key为keys,值为JWK对象数组)

3. 增加JWK空值判断逻辑

当前代码未处理getKeyByKeyId返回null的情况,会直接触发空指针异常。需补充判空:

if (jwk == null) {
    throw new RuntimeException("Public key not found for kid: " + kid);
}

4. 规避Keycloak内置公钥存储干扰

日志中提到的InfinispanPublicKeyStorageProvider是Keycloak内置公钥存储,若完全使用自定义外部JWK验签,需确认自定义流程未误触发内置验签逻辑,确保所有验签操作通过自定义代码完成。

5. 优化JWK获取性能(可选)

每次认证都调用外部API获取JWK Set会影响性能,还可能触发API限流。可基于cache-control响应头或定时策略,将JWK Set缓存到本地或Keycloak内置缓存中。

完整修复后的verifySignature方法示例

private boolean verifySignature(String jwt, JWKSet euroInfoJwkSet) throws Exception {
    String[] jwtParts = jwt.split("\\.");
    if (jwtParts.length != 3) {
        throw new IllegalArgumentException("Invalid JWT format");
    }

    // 解码Header提取kid
    String headerJson = new String(Base64.getUrlDecoder().decode(jwtParts[0]), StandardCharsets.UTF_8);
    JsonObject header = JsonParser.parseString(headerJson).getAsJsonObject();
    if (!header.has("kid")) {
        throw new IllegalArgumentException("JWT Header missing kid field");
    }
    String kid = header.get("kid").getAsString();

    // 获取对应JWK并验证
    JWK jwk = euroInfoJwkSet.getKeyByKeyId(kid);
    if (jwk == null) {
        throw new RuntimeException("Public key not found for kid: " + kid);
    }
    if (!(jwk instanceof RSAKey)) {
        throw new IllegalArgumentException("Unsupported key type: " + jwk.getKeyType());
    }
    PublicKey publicKey = ((RSAKey) jwk).toPublicKey();

    Signature sig = Signature.getInstance("SHA256withRSA");
    sig.initVerify(publicKey);
    sig.update((jwtParts[0] + "." + jwtParts[1]).getBytes(StandardCharsets.UTF_8));
    byte[] signatureBytes = Base64.getUrlDecoder().decode(jwtParts[2]);
    return sig.verify(signatureBytes);
}

内容的提问来源于stack exchange,提问作者mohamed amine salah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:25:19