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

如何在KMM Ktor客户端中配置信任自签名SSL证书?

Ktor 客户端信任自签名证书配置方案

不要用全局禁用证书校验的野路子,会直接失去HTTPS的防中间人能力,正确方案是把自签名根证书内置到客户端,配置客户端仅信任这张证书。另外提前确认:你生成自签证书时已经把服务端静态IP填到了SAN(主题备用名称)字段,否则就算加了信任也会报主机名不匹配错误。

前置准备

把服务端的自签名根证书(crt/cer/pem格式都可以)导出,分别放到两端工程的资源目录:

  • Android端:放到app/src/main/res/raw/目录,命名比如self_signed_cert.crt
  • iOS端:把证书拖入iOS工程Resources组,勾选对应编译target即可,不需要在Info.plist里全局放开ATS限制。

跨层配置逻辑

因为SSL信任配置是平台相关能力,用KMM的expect/actual机制实现:
首先在commonMain里定义期望方法:

// commonMain 路径下的SslConfig.kt
import io.ktor.client.*

expect fun HttpClientConfig<*>.configureSelfSignedCertTrust()

在你原来的ApiGateway初始化HttpClient的代码块里,加上这行配置调用:

private val client = HttpClient {
    installContentNegotiation()
    installLogging()
    configureRequest()
    configureResponseValidator()
    installAuth()
    configureSelfSignedCertTrust() // 新增这行
}

Android端实际实现(androidMain)

Ktor Android端默认用OkHttp引擎,加载内置证书构造自定义信任管理器即可:

// androidMain 路径下的SslConfig.kt
import android.content.Context
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import okhttp3.CertificatePinner
import java.io.InputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

private fun Context.buildSelfSignedTrustManager(): X509TrustManager {
    val certInput: InputStream = resources.openRawResource(R.raw.self_signed_cert)
    val certFactory = CertificateFactory.getInstance("X.509")
    val selfSignedCert = certFactory.generateCertificate(certInput) as X509Certificate
    certInput.close()

    val trustStore = KeyStore.getInstance(KeyStore.getDefaultType())
    trustStore.load(null, null)
    trustStore.setCertificateEntry("server_self_signed", selfSignedCert)

    val tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    tmFactory.init(trustStore)
    return tmFactory.trustManagers.first() as X509TrustManager
}

actual fun HttpClientConfig<*>.configureSelfSignedCertTrust() {
    engine {
        // 替换成你项目里能拿到的Application Context
        val appContext: Context = /* 自行注入Application Context */
        val trustManager = appContext.buildSelfSignedTrustManager()
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, arrayOf(trustManager), null)

        config {
            sslSocketFactory(sslContext.socketFactory, trustManager)
            // 可选:加证书公钥锁定,进一步降低中间人风险
            val certPinner = CertificatePinner.Builder()
                // 替换为你的服务IP+端口,以及证书对应的SHA256公钥指纹
                .add("xxx.xxx.xxx.xxx:端口号", "sha256/证书SHA256指纹")
                .build()
            certificatePinner(certPinner)
            // 不要写hostnameVerifier { _,_ -> true } 这种全局放开校验的代码,证书SAN配置正确的话不需要
        }
    }
}

证书SHA256公钥指纹获取:本地终端执行openssl x509 -in 你的证书文件.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64,输出结果就是需要填入的指纹值。

iOS端实际实现(iosMain)

iOS端用Darwin引擎,自定义URLSession挑战处理逻辑,把内置证书设为唯一信任锚点:

// iosMain 路径下的SslConfig.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
import platform.Foundation.*
import platform.Security.*

actual fun HttpClientConfig<*>.configureSelfSignedCertTrust() {
    engine {
        // 从App Bundle加载内置证书
        val certPath = NSBundle.mainBundle.pathForResource("self_signed_cert", ofType = "crt")
            ?: throw IllegalArgumentException("自签名证书未找到,请检查iOS工程资源配置")
        val certData = NSData.dataWithContentsOfFile(certPath)
            ?: throw IllegalArgumentException("自签名证书读取失败")
        val anchorCert = SecCertificateCreateWithData(null, certData)
            ?: throw IllegalArgumentException("证书格式解析失败")

        handleChallenge { _, challenge, completionHandler ->
            val protectionSpace = challenge.protectionSpace
            if (protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
                val serverTrust = protectionSpace.serverTrust!!
                // 将内置证书设为唯一可信锚点,不使用系统内置信任根证书
                SecTrustSetAnchorCertificates(serverTrust, arrayOf(anchorCert))
                SecTrustSetAnchorCertificatesOnly(serverTrust, true)

                var evalError: CFErrorRef? = null
                val trustResult = SecTrustEvaluateWithError(serverTrust, evalError?.ptr)
                if (trustResult) {
                    completionHandler(
                        NSURLSessionAuthChallengeUseCredential,
                        NSURLCredential.credentialForTrust(serverTrust)
                    )
                } else {
                    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, null)
                }
            } else {
                completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, null)
            }
        }
    }
}

避坑说明

  • 自签证书必须配置SAN字段包含你访问用的静态IP,生成证书时openssl配置里要加subjectAltName = IP:你的服务端静态IP,否则两端都会报证书身份不匹配错误。
  • 后续如果服务端更换证书,必须同步更新客户端内置的证书文件,否则会出现SSL握手失败,建议提前做证书预下发逻辑降低发版依赖。
  • 禁止使用信任所有证书、跳过所有主机名校验的实现,就算是内部服务也存在被抓包泄露数据的风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 09:09:42