在 Keycloak 21.0.0 中,使用提供 JSON Web 密钥 (JWK) 的外部 API 时,在签名验证过程中会出现问题。系统无法找到预期的公钥,记录以下警告和错误消息:
警告 [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] 在存储中找不到 PublicKey。要求的孩子: 'sig-2024-10-07-DB'。可用的孩子:“[]”警告[org.keycloak.services] KC-SERVICES0097:无效请求:java.lang.RuntimeException:失败 验证“请求”对象上的签名
@Override
public void authenticate(AuthenticationFlowContext context) {
JWKSet euroInfoJwkSet = fetchJWKSet();
boolean isValid = verifySignature(decryptedJwt, euroInfoJwkSet);
if (!isValid) {
context.failure(GENERIC_AUTHENTICATION_ERROR);
return;
}
}
private JWKSet fetchJWKSet() throws Exception {
URL url = new URL("https://request/jwk_request.cgi?client_id=5g4h4r8r");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed to fetch JWKs: HTTP error code " + conn.getResponseCode());
}
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
Gson gson = new Gson();
return JWKSet.parse(response.toString());
}
private boolean verifySignature(String jwt, JWKSet euroInfoJwkSet) throws Exception {
String[] jwtParts = jwt.split("\\.");
if (jwtParts.length != 3) {
throw new IllegalArgumentException("Invalid JWT format");
}
byte[] signatureBytes = Base64.getUrlDecoder().decode(jwtParts[2]);
JWK jwk = euroInfoJwkSet.getKeyByKeyId(jwtParts[0]);
PublicKey publicKey = ((RSAKey) jwk).toPublicKey();
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update((jwtParts[0] + "." + jwtParts[1]).getBytes());
return sig.verify(signatureBytes);
}
我通过调整Keycloak中的客户端身份验证流程解决了这个问题。具体来说,我将客户端更改为机密模式。设置完成后,客户端配置下会出现一个新选项卡,我可以在其中输入 JWK URL。
Keycloak 在使用 /auth 路径时处理整个身份验证过程。它会自动从提供的 URL 检索 JWK,验证签名,解密消息,并将结果存储在会话中,解决公钥验证问题。