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

求助:如何在Ktor多平台(KMM)中实现SSL固定?

Ktor多平台(KMM)实现SSL固定指南

一、前置准备

将你的.der证书放置到对应平台的资源目录:

  • Android:src/androidMain/res/raw/
  • iOS:src/iosMain/resources/

二、共享代码基础配置

在共享模块(commonMain)中,通过expect/actual机制定义平台专属的SSL校验逻辑入口,再统一配置HttpClient:

1. 定义平台期望函数

// commonMain/utils/SSLConfig.kt
expect fun getSSLEngineConfig(): SSLEngineConfig?

2. 共享HttpClient配置

// commonMain/network/ApiClient.kt
val httpClient = HttpClient(
    // 根据平台自动选择OkHttp(Android)或Ios引擎
    if (Platform.isAndroid) OkHttp else Ios
) {
    engine {
        getSSLEngineConfig()?.let { config ->
            when (this) {
                is OkHttpEngineConfig -> {
                    sslSocketFactory(config.socketFactory, config.trustManager)
                    hostnameVerifier { _, session ->
                        session.peerHost == "你的API域名"
                    }
                }
                is IosEngineConfig -> {
                    configureSession {
                        configuration = config.iosSessionConfig
                        URLSessionDelegate = config.iosSessionDelegate
                    }
                }
            }
        }
    }
    // 其他通用配置:内容解析、日志等
    install(ContentNegotiation) {
        json()
    }
}

// 辅助数据类,统一传递平台SSL配置
data class SSLEngineConfig(
    val socketFactory: SSLSocketFactory? = null,
    val trustManager: X509TrustManager? = null,
    val iosSessionConfig: NSURLSessionConfiguration? = null,
    val iosSessionDelegate: NSURLSessionDelegateProtocol? = null
)

三、Android端实现(androidMain)

加载本地.der证书,生成自定义TrustManager和SSLSocketFactory:

// androidMain/utils/SSLConfig.kt
actual fun getSSLEngineConfig(): SSLEngineConfig {
    val certificateFactory = CertificateFactory.getInstance("X.509")
    val inputStream = context.resources.openRawResource(R.raw.your_certificate) // 替换为你的证书文件名
    val certificate = certificateFactory.generateCertificate(inputStream)
    inputStream.close()

    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
        load(null, null)
        setCertificateEntry("ca", certificate)
    }

    val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(keyStore)
    }
    val trustManager = trustManagerFactory.trustManagers.first { it is X509TrustManager } as X509TrustManager

    val sslContext = SSLContext.getInstance("TLS").apply {
        init(null, trustManagerFactory.trustManagers, SecureRandom())
    }

    return SSLEngineConfig(
        socketFactory = sslContext.socketFactory,
        trustManager = trustManager
    )
}

注:context可通过Android全局上下文单例获取,或从业务代码传入。

四、iOS端实现(iosMain)

通过NSURLSession的代理方法完成证书校验,替换Ktor默认的会话配置:

// iosMain/utils/SSLConfig.kt
actual fun getSSLEngineConfig(): SSLEngineConfig {
    val certificatePath = NSBundle.mainBundle.pathForResource("your_certificate", "der")!!
    val certificateData = NSData.dataWithContentsOfFile(certificatePath)!!
    val localCert = SecCertificateCreateWithData(null, certificateData)!!

    val sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration().apply {
        URLCredentialStorage = null
    }

    val sessionDelegate = object : NSURLSessionDelegateProtocol {
        override fun URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit) {
            if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
                val serverTrust = challenge.protectionSpace.serverTrust!!
                val serverCerts = SecTrustCopyCertificates(serverTrust) as Array<SecCertificate>
                
                // 对比服务器证书与本地证书
                val isMatch = serverCerts.any { SecCertificateCompare(it, localCert) == 0 }
                if (isMatch) {
                    val credential = NSURLCredential.createForTrust(serverTrust)
                    completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
                } else {
                    completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, null)
                }
            } else {
                completionHandler(NSURLSessionAuthChallengeDisposition.PerformDefaultHandling, null)
            }
        }
    }

    return SSLEngineConfig(
        iosSessionConfig = sessionConfig,
        iosSessionDelegate = sessionDelegate
    )
}

注:确保Xcode中已将.der证书添加到项目资源并勾选对应target。

五、核心注意事项

  • 务必保留主机名验证逻辑,避免中间人攻击风险。
  • 证书文件名需在各平台保持一致,避免加载失败。
  • 若证书过期,需及时替换对应平台资源目录中的文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:43:11