Spring Cloud Gateway无法找到有效证书路径的原因及解决方法
Keycloak+Spring Cloud Gateway自签名证书PKIX路径问题排查与解决
问题背景
使用Keycloak对Spring Cloud Gateway做安全保护,Keycloak采用以下命令生成的自签名证书:
openssl genrsa -out ca.key 2048 openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=Acme Root CA" -out ca.crt openssl req -newkey rsa:2048 -nodes -keyout server.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=*my-site.com" -out server.csr openssl x509 -req -extfile <(printf "subjectAltName=DNS:my-site.com") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
在Spring Cloud Gateway的Dockerfile中已添加该自签名证书的CA证书:
USER root # Copy the certificate file into the image COPY --from=build /app/certs/ca.crt /usr/local/share/ca-certificates/ca.crt # Change the permissions of the /etc/ssl/certs directory RUN chmod a+w /etc/ssl/certs # Update the certificate store RUN update-ca-certificates
但登录输入凭据后,Spring Cloud Gateway抛出以下错误:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.validator.PKIXValidator.doBuild(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.validator.PKIXValidator.engineValidate(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.validator.Validator.validate(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | ... 30 common frames omitted core_api-gateway.1.4z2matjvgalf@ws002cfw | Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | at java.base/java.security.cert.CertPathBuilder.build(Unknown Source) ~[na:na] core_api-gateway.1.4z2matjvgalf@ws002cfw | ... 36 common frames omitted
问题原因
- Java信任库未同步:
update-ca-certificates更新的是系统级证书存储,但Java应用默认使用自身的cacerts信任库,不会自动读取系统证书。 - 证书域名不匹配:Keycloak的实际访问域名,未包含在证书的
CN或subjectAltName字段中,导致证书身份验证失败。 - 证书链配置不完整:Keycloak仅配置了服务器证书,未关联CA根证书,Java无法追溯到可信的根证书节点。
解决方法
方法1:将CA证书导入Java信任库
修改Dockerfile,把CA证书导入Java自带的cacerts文件(注意路径随Java版本调整,示例为OpenJDK 17):
USER root # 复制CA证书到临时目录 COPY --from=build /app/certs/ca.crt /tmp/ca.crt # 导入到Java信任库,默认密码为changeit RUN keytool -importcert -file /tmp/ca.crt -alias acme-root-ca -keystore /usr/local/openjdk-17/lib/security/cacerts -storepass changeit -noprompt # 可选:更新系统证书存储,适配其他工具 COPY --from=build /app/certs/ca.crt /usr/local/share/ca-certificates/ca.crt RUN update-ca-certificates # 恢复非root用户,符合安全规范 USER nonroot
方法2:让Java使用系统证书存储
在Gateway启动参数中添加JVM参数,指定Java读取系统证书:
-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
也可以在Dockerfile中通过环境变量配置:
ENV JAVA_OPTS="-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit"
方法3:修正证书与Keycloak配置
- 调整证书域名:如果Keycloak访问域名是
keycloak.my-site.com,重新生成证书时修改subjectAltName为DNS:my-site.com,DNS:keycloak.my-site.com。 - 完善Keycloak证书链:确保Keycloak配置中同时加载
server.crt、server.key和ca.crt,让证书链完整可追溯。
方法4:临时跳过证书验证(仅测试环境)
测试阶段可临时关闭证书验证,生产环境绝对禁止:
添加JVM参数:
-Djavax.net.ssl.trustStoreType=JKS -Dcom.sun.net.ssl.checkRevocation=false -Djdk.tls.disableEndpointIdentificationAlgorithm=true
或通过Spring配置自定义WebClient跳过验证:
@Bean public WebClient webClient() { HttpClient httpClient = HttpClient.create().secure(sslContextSpec -> sslContextSpec .sslContext(SSLContextBuilder.create().loadTrustMaterial((chain, authType) -> true))); return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build(); }
内容的提问来源于stack exchange,提问作者Kambei
相关产品推荐
相关产品推荐

