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
相关产品推荐
相关产品推荐

