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

Spring应用从GCP Secret Manager获取密钥遇SSL证书错误求助

问题:Spring应用访问GCP Secret Manager时出现SSL证书错误

我按照GCP官方文档实现从Secret Manager获取密钥,应用基于Spring框架,主方法先通过SpringApplication.run启动容器,但运行时出现SSL握手失败的错误,错误栈如下:

Exception in thread "main"
com.google.api.gax.rpc.UnavailableException:
io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to
obtain metadata     at
com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:67)
    at
com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:72)
    at
com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:60)
    at
com.google.api.gax.grpc.GrpcExceptionCallable$ExceptionTransformingFuture.onFailure(GrpcExceptionCallable.java:97)
    at com.google.api.core.ApiFutures$1.onFailure(ApiFutures.java:67)   at
com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1132)
    at
com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
    at
com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1270)
    at
com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:1038)
    at
com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:808)
    at
io.grpc.stub.ClientCalls$GrpcFuture.setException(ClientCalls.java:563)
    at
io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:533)
    at
io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
    at
io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
    at
io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
    at
com.google.api.gax.grpc.ChannelPool$ReleasingClientCall$1.onClose(ChannelPool.java:535)
    at
io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:562)
    at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:70)
    at
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:743)
    at
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:722)
    at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)    at
io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
    at
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:858)  Suppressed:
com.google.api.gax.rpc.AsyncTaskException: Asynchronous task failed
        at
com.google.api.gax.rpc.ApiExceptions.callAndTranslateApiException(ApiExceptions.java:57)
        at com.google.api.gax.rpc.UnaryCallable.call(UnaryCallable.java:112)
        at
com.google.cloud.secretmanager.v1.SecretManagerServiceClient.accessSecretVersion(SecretManagerServiceClient.java:1279)
        at
com.google.cloud.secretmanager.v1.SecretManagerServiceClient.accessSecretVersion(SecretManagerServiceClient.java:1215)
        at
com.recontool.readSecrets.AccessSecretVersion.accessSecretVersion(AccessSecretVersion.java:39)
        at
com.recontool.readSecrets.AccessSecretVersion.main(AccessSecretVersion.java:25)
Caused by: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials
failed to obtain metadata   at
io.grpc.StatusRuntimeException.fillInStackTrace(StatusRuntimeException.java:68)
    at
io.grpc.StatusRuntimeException.<init>(StatusRuntimeException.java:58)
    at
io.grpc.StatusRuntimeException.<init>(StatusRuntimeException.java:50)
    at io.grpc.Status.asRuntimeException(Status.java:535)   ... 14 more
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building
failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target     at
java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at
java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371)
    at
java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314)
    at
java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:309)
    at
java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1351)
    at
java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1226)
    at
java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1169)
    at
java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
    at
java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:480)
    at
java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:458)
    at
java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:201)
    at
java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at
java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1505)
    at
java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1420)
    at
java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
    at
java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
    at
java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:578)
    at
java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183)
    at
java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1430)
    at
java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1401)
    at
java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:220)
    at
com.google.api.client.http.javanet.NetHttpRequest.execute(NetHttpRequest.java:113)
    at
com.google.api.client.http.javanet.NetHttpRequest.execute(NetHttpRequest.java:84)
    at
com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1012)
    at
com.google.auth.oauth2.UserCredentials.doRefreshAccessToken(UserCredentials.java:277)
    at
com.google.auth.oauth2.UserCredentials.refreshAccessToken(UserCredentials.java:191)
    at
com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:257)
    at
com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:254)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    ... 3 more Caused by: sun.security.validator.ValidatorException: PKIX
path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target   at
java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:388)
    at
java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:271)
    at
java.base/sun.security.validator.Validator.validate(Validator.java:256)
    at
java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
    at
java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
    at
java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335)
    ... 27 more Caused by:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target   at
java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at
java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at
java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
    at
java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:383)
    ... 32 more

我的代码片段如下:

public static void main(String[] args) throws IOException {
        SpringApplication.run(AccessSecretVersion.class, args);
        String projectId = ..
        String secretId = ..
        String versionId = "2";
        accessSecretVersion(projectId, secretId, versionId);
    }
    
    public static void accessSecretVersion(String projectId, String secretId, String versionId)
            throws IOException {
        // Initialize client that will be used to send requests. This client only needs to be created
        // once, and can be reused for multiple requests. After completing all of your requests, call
        // the "close" method on the client to safely clean up any remaining background resources.
        try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) {
            SecretVersionName secretVersionName = SecretVersionName.of(projectId, secretId, versionId);

            // Access the secret version.
            AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);

            // Verify checksum. The used library is available in Java 9+.
            // If using Java 8, you may use the following:
            // https://github.com/google/guava/blob/e62d6a0456420d295089a9c319b7593a3eae4a83/guava/src/com/google/common/hash/Hashing.java#L395
            byte[] data = response.getPayload().getData().toByteArray();
            Checksum checksum = new CRC32C();
            checksum.update(data, 0, data.length);
            if (response.getPayload().getDataCrc32C() != checksum.getValue()) {
                System.out.printf("Data corruption detected.");
                return;
            }

            // Print the secret payload.
            //
            // WARNING: Do not print the secret in a production environment - this
            // snippet is showing how to access the secret material.
            String payload = response.getPayload().getData().toStringUtf8();
            System.out.printf("Plaintext: %s\n", payload);
        }
    }
}

请问我哪里操作有误,该如何解决这个问题?


解决方案

1. 核心问题:SSL证书信任缺失

错误栈最底层是SunCertPathBuilderException: unable to find valid certification path to requested target,说明JVM不信任GCP服务端的SSL证书,通常是因为:

  • 本地环境没有导入GCP根证书
  • 应用运行在代理环境下,代理证书未被JVM信任

2. Spring应用的代码写法问题

你在main方法启动Spring容器后直接调用静态方法accessSecretVersion,这种写法不符合Spring的生命周期管理,应该:

  • 将Secret Manager客户端配置为Spring Bean,由Spring管理生命周期
  • 在Spring的初始化回调(如@PostConstruct)中执行密钥获取逻辑

修正后的代码示例:

@SpringBootApplication
public class AccessSecretVersion {

    private final String projectId = "你的项目ID";
    private final String secretId = "你的密钥ID";
    private final String versionId = "2";

    public static void main(String[] args) {
        SpringApplication.run(AccessSecretVersion.class, args);
    }

    @Bean
    public SecretManagerServiceClient secretManagerServiceClient() throws IOException {
        return SecretManagerServiceClient.create();
    }

    @PostConstruct
    public void init() throws IOException {
        try (SecretManagerServiceClient client = secretManagerServiceClient()) {
            SecretVersionName secretVersionName = SecretVersionName.of(projectId, secretId, versionId);
            AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);

            byte[] data = response.getPayload().getData().toByteArray();
            Checksum checksum = new CRC32C();
            checksum.update(data, 0, data.length);
            if (response.getPayload().getDataCrc32C() != checksum.getValue()) {
                System.out.printf("Data corruption detected.");
                return;
            }

            String payload = response.getPayload().getData().toStringUtf8();
            System.out.printf("Plaintext: %s\n", payload);
        }
    }
}

3. 解决SSL证书问题的具体步骤

方法一:导入GCP根证书到JVM信任库

  1. 获取GCP服务端对应的根证书
  2. 使用keytool命令导入证书到JVM的cacerts信任库:
keytool -importcert -file <证书文件路径> -alias gcp-root -keystore <JDK安装路径>/jre/lib/security/cacerts

默认信任库密码为changeit

方法二:配置JVM信任自定义证书

启动应用时添加JVM参数,指定包含GCP证书的自定义信任库:

java -Djavax.net.ssl.trustStore=<自定义信任库路径> -Djavax.net.ssl.trustStorePassword=<信任库密码> -jar 你的应用.jar

方法三:使用GCP SDK自动处理证书(推荐)

确保应用依赖最新版本的GCP Secret Manager SDK,它会自动处理证书信任逻辑。同时正确配置GCP认证:

  • 本地开发时,设置环境变量GOOGLE_APPLICATION_CREDENTIALS指向你的服务账号密钥文件
  • 部署到GCP环境时,使用默认应用身份认证,无需额外配置

4. 认证配置检查

确保你的服务账号拥有roles/secretmanager.secretAccessor权限,并且:

  • 本地开发时,GOOGLE_APPLICATION_CREDENTIALS环境变量已正确设置
  • 代码中没有覆盖默认的GCP认证配置

内容的提问来源于stack exchange,提问作者David Gordon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 05:11:11