求助:如何在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
相关产品推荐
相关产品推荐

