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

GRPC调用遇UNAVAILABLE错误:证书信任锚未找到(Android)

Android Kotlin gRPC调用SSL握手失败(Trust anchor not found)问题

问题概述

在Android环境下用Kotlin编写gRPC代码,已添加Authorization等请求头,调用grpc.friday.fastek.ph的stmBalance接口时返回UNAVAILABLE状态,但Postman可正常调用。堆栈错误根源为javax.net.ssl.SSLHandshakeException,具体是java.security.cert.CertPathValidatorException: Trust anchor for certification path not found,尝试过Android安全配置文档仍未解决。

代码片段

val port = 9090
val metadata = Metadata()
metadata.put(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJCTzdIZWwwSkRpc0YxR1JWd21oZmRxanpJZFdLS1VlX3htWEVkTndsNzZ3In0.eyJleHAiOjE3MTUxNTg0NzksImlhdCI6MTcxNTE1NjY3OSwianRpIjoiZDExMTYwZDEtNGYxZS00ZTgxLTg0NDQtMjQwMjZjMzg5ODM1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLnYyLmZyaWRheS5mYXN0ZWsucGgvcmVhbG1zL0Zhc3Rlay1BdXRoIiwic3ViIjoiM2ZkMDJlZTEtZTk5Ni00N2QzLWEwM2QtMGIyYWVhMmM1MjE5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiSzI1ODIzNzMwMDAyOSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJJbWFnZSBQcm9jZXNzaW5nIEFjY291bnQiLCJQYXJ0bmVyIFNlcnZpY2UgQWNjb3VudCJdfSwic2NvcGUiOiIiLCJjbGllbnRIb3N0IjoiMzUuMjI5LjIzMi40NSIsImNsaWVudElkIjoiSzI1ODIzNzMwMDAyOSIsImNsaWVudEFkZHJlc3MiOiIzNS4yMjkuMjMyLjQ1In0.P3uIa7S36cLN3w49yeeyf0Ddd5nibQuE_nzdJzBOQewjIBwqGPTsPHvtsa59Ldnc_enYbjxzURoJ5ho-FeQZwuIEb2iM5yB_UrzPDFgZbsUBwNH3-Efdv1W-7lS-4XLne4BmT-7fP-H6EFjVTD8pDArSeX53O6fXn79VJJaEFYpZ7CGqEc2NoBmi8dhKpzcJ7ii1-kkPm3sSOiNwjDannXTXy8hA-INuAyE_dZu7souHN_IJL6e7yw8ueeo-eXDPwjB9QxAR09aRfFNGkuzgghPzeIsJAeaf8GLRdNDzF4YB6klaCPOM19-FNuD7P3pdLfAKqD-Dxa2jPl8jjODRow")
metadata.put(Metadata.Key.of("txn-mgr-flag", Metadata.ASCII_STRING_MARSHALLER), "false")
        
val channel = ManagedChannelBuilder
            .intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
            .forAddress("grpc.friday.fastek.ph", port)
            .useTransportSecurity()
            .build()
try {
    val client = FastekBeepClient(channel)
    client.stmBalance(intialRequest)
} catch (e: Exception) {
    // 异常处理
}

调用方式

client.stmBalance(intialRequest)

错误堆栈

2024-05-08 11:43:44.650 32644-32644 AndroidRuntime         
ph.skybridge.android                 E  FATAL EXCEPTION: main
Process: ph.skybridge.android, PID: 32644
io.grpc.StatusException: UNAVAILABLE
    at io.grpc.Status.asException(Status.java:548)
    at io.grpc.kotlin.ClientCalls$rpcImpl$1$1$1.onClose(ClientCalls.kt:300)
    at io.grpc.internal.DelayedClientCall$DelayedListener$3.run(DelayedClientCall.java:489)
    at io.grpc.internal.DelayedClientCall$DelayedListener.delayOrExecute(DelayedClientCall.java:453)
    at io.grpc.internal.DelayedClientCall$DelayedListener.onClose(DelayedClientCall.java:486)
    at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:564)
    at io.grpc.internal.ClientCallImpl.access$100(ClientCallImpl.java:72)
    at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:729)
    at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:710)
    at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
    at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
    at java.lang.Thread.run(Thread.java:761)
    Suppressed:
kotlinx.coroutines.internal.DiagnosticCoroutineContextException:
[StandaloneCoroutine{Cancelling}@d8394aa, Dispatchers.Main]
Caused by: javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:361)
    at io.grpc.okhttp.OkHttpProtocolNegotiator.negotiate(OkHttpProtocolNegotiator.java:100)
    at io.grpc.okhttp.OkHttpProtocolNegotiator$AndroidNegotiator.negotiate(OkHttpProtocolNegotiator.java:218)
    at io.grpc.okhttp.OkHttpTlsUpgrader.upgrade(OkHttpTlsUpgrader.java:63)
    at io.grpc.okhttp.OkHttpClientTransport$3.run(OkHttpClientTransport.java:552)
    ... 4 more
Caused by: java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:549)
    at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:401)
    at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:375)
    at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:304)
    at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
    at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)
    at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:178)
    at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:596)
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native
Method)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
    ... 8 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    ... 18 more

解决方案

1. 导出服务器根CA证书

Postman能正常访问是因为它内置了完整的根证书库,而Android设备/模拟器可能缺少目标服务器对应的根CA:

  • 用浏览器访问https://grpc.friday.fastek.ph:9090(注意端口),查看证书详情,找到根CA证书并导出为PEM格式(文本格式,开头包含-----BEGIN CERTIFICATE-----)。

2. 配置Android网络安全文件

  • 在res/xml目录下创建network_security_config.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config>
            <domain includeSubdomains="true">grpc.friday.fastek.ph</domain>
            <trust-anchors>
                <certificates src="@raw/root_ca"/> <!-- 替换为你的证书文件名 -->
                <certificates src="system"/> <!-- 保留系统默认信任的证书 -->
            </trust-anchors>
        </domain-config>
    </network-security-config>
    
  • 将导出的PEM证书放到res/raw目录下,命名为root_ca.pem(和xml中引用的名字一致)。
  • 在AndroidManifest.xml的<application>标签中添加配置:
    <application
        ...
        android:networkSecurityConfig="@xml/network_security_config">
    

3. 代码中直接配置gRPC的SSLContext(备选方案)

如果安全配置未生效,可以直接在代码中给gRPC Channel指定自定义信任管理器:

// 加载根CA证书并创建TrustManager
private fun getCustomTrustManager(context: Context): X509TrustManager {
    val certInputStream = context.resources.openRawResource(R.raw.root_ca)
    val certFactory = CertificateFactory.getInstance("X.509")
    val certificates = certFactory.generateCertificates(certInputStream)
    
    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
        load(null)
        certificates.forEachIndexed { index, cert ->
            setCertificateEntry("ca_$index", cert)
        }
    }
    
    val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(keyStore)
    }
    
    return trustManagerFactory.trustManagers.first() as X509TrustManager
}

// 构建gRPC Channel时指定SSLContext
val sslContext = SSLContext.getInstance("TLS").apply {
    init(null, arrayOf(getCustomTrustManager(context)), SecureRandom())
}

val channel = ManagedChannelBuilder
    .intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
    .forAddress("grpc.friday.fastek.ph", port)
    .useTransportSecurity()
    .sslContext(sslContext)
    .build()

4. 常见坑排查

  • 必须导出根CA证书,不是服务器的叶子证书,否则Android仍无法找到信任锚。
  • 确保证书是PEM格式,若为DER二进制格式,需要转换为PEM(可以用OpenSSL命令:openssl x509 -inform der -in cert.cer -out cert.pem)。
  • 检查network_security_config.xml中的domain字段和服务器地址完全匹配,includeSubdomains按需设置。
  • 正式环境禁止使用允许所有证书的调试配置,必须严格配置信任链。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 09:03:11