Spring Boot对接Keycloak验证JWT时出现SSL证书信任问题
Spring Boot响应式应用对接Keycloak(LetsEncrypt证书)JWT验证PKIX错误解决方案
问题背景
Spring Boot响应式应用与使用LetsEncrypt SSL证书的Keycloak服务器通信时,验证JWT出现Could not obtain the keys错误,根因是PKIX path building failed: unable to find valid certification path to requested target。已尝试将证书添加到$JAVA_HOME/lib/security/cacerts但未解决。
解决方案
1. 确认证书导入的是应用实际使用的JVM cacerts
很多时候应用运行的JVM并非系统的$JAVA_HOME(比如IDE内置JVM、容器内JVM),需先定位应用实际使用的JVM路径:
# Linux下查看应用进程的JVM路径 ps aux | grep java # 或查看应用启动日志中的JVM路径信息
用keytool验证证书是否存在于对应JVM的cacerts中:
keytool -list -keystore /path/to/actual/jre/lib/security/cacerts -alias letsencryptroot # 默认密码为changeit
若未找到,重新导入LetsEncrypt根证书(ISRG Root X1):
# 下载根证书 curl -O https://letsencrypt.org/certs/isrgrootx1.pem # 导入到目标cacerts keytool -importcert -file isrgrootx1.pem -alias letsencryptroot -keystore /path/to/actual/jre/lib/security/cacerts -storepass changeit -noprompt
2. 配置Spring Boot直接指定信任证书
无需依赖系统cacerts,直接在应用中配置信任证书:
- 将
isrgrootx1.pem放入项目src/main/resources目录 - 修改
application.yml添加SSL配置:
spring: ssl: bundle: jks: keycloak-truststore: source: type: PEM certificate: classpath:isrgrootx1.pem
- 在
ReactiveSecurityConfig中配置JWT解码器时指定信任存储:
@Bean public ReactiveJwtDecoder reactiveJwtDecoder() throws Exception { SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(new ClassPathResource("isrgrootx1.pem"), (chain, authType) -> true) .build(); HttpClient httpClient = HttpClient.create() .secure(sslSpec -> sslSpec.sslContext(sslContext)); return NimbusReactiveJwtDecoder.withJwkSetUri("https://your-keycloak-domain/auth/realms/your-realm/protocol/openid-connect/certs") .httpClient(httpClient) .build(); }
3. 检查Keycloak证书链完整性
若Keycloak未返回完整证书链(仅返回叶子证书,缺少中间证书),也会导致PKIX错误:
# 检查Keycloak证书链 openssl s_client -connect your-keycloak-domain:443 -showcerts
若链不完整,需在Keycloak服务器上将叶子证书与中间证书合并为一个完整文件后重新配置。
4. 临时禁用SSL验证(仅测试环境)
生产环境绝对禁止,仅用于临时测试排查:
@Bean public ReactiveJwtDecoder reactiveJwtDecoder() throws Exception { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new SecureRandom()); HttpClient httpClient = HttpClient.create() .secure(sslSpec -> sslSpec.sslContext(sslContext) .defaultConfiguration(defaultConfig -> defaultConfig .handlerConfigurator(handler -> handler.getSslEngineConfigurator().setHostnameVerifier((s, sslSession) -> true)))); return NimbusReactiveJwtDecoder.withJwkSetUri("https://your-keycloak-domain/auth/realms/your-realm/protocol/openid-connect/certs") .httpClient(httpClient) .build(); }
内容的提问来源于stack exchange,提问作者Walid Bahhou
相关产品推荐
相关产品推荐

