Ktor集成OkHttp如何使用key和pem证书发起mTLS POST请求
Ktor(OkHttp引擎)双向TLS配置实现
以下实现完全对齐给出的curl命令行为,不需要额外配置服务端信任证书,直接加载本地私钥和PEM证书即可完成mTLS握手:
前置依赖
先在build.gradle.kts中引入必要依赖:
dependencies { implementation("io.ktor:ktor-client-core:2.3.7") implementation("io.ktor:ktor-client-okhttp:2.3.7") // 按需引入序列化、日志等其他组件 }
如果你的私钥是-----BEGIN RSA PRIVATE KEY-----开头的PKCS#1格式,额外加BouncyCastle依赖用于解析:
implementation("org.bouncycastle:bcpkix-jdk15on:1.77")
也可以直接用openssl命令把私钥转成通用PKCS#8格式,避免额外依赖:
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in mykey.key -out mykey_pkcs8.key
完整实现代码
import io.ktor.client.* import io.ktor.client.engine.okhttp.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import org.bouncycastle.openssl.PEMKeyPair import org.bouncycastle.openssl.PEMParser import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter import java.io.File import java.io.FileReader import java.security.KeyFactory import java.security.KeyStore import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.spec.PKCS8EncodedKeySpec import javax.net.ssl.KeyManagerFactory import javax.net.ssl.SSLContext import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager fun buildMtlsClient(): HttpClient { // 加载PEM格式客户端证书 val certFactory = CertificateFactory.getInstance("X.509") val clientCert = certFactory.generateCertificate(File("mycert.pem").inputStream()) as X509Certificate // 加载私钥 val privateKey = if (File("mykey.key").readText().startsWith("-----BEGIN PRIVATE KEY-----")) { // 解析PKCS#8格式私钥 val keyContent = File("mykey.key").readText() .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replace("\\s".toRegex(), "") val keyBytes = java.util.Base64.getDecoder().decode(keyContent) KeyFactory.getInstance("RSA").generatePrivate(PKCS8EncodedKeySpec(keyBytes)) } else { // 解析PKCS#1格式私钥 val pemParser = PEMParser(FileReader("mykey.key")) val keyPair = pemParser.readObject() as PEMKeyPair JcaPEMKeyConverter().getKeyPair(keyPair).private } // 构造存储客户端身份凭证的KeyStore val clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { load(null, null) setKeyEntry( "mtls-key", privateKey, // 私钥设置了密码就替换成对应字符数组,无密码传空数组 CharArray(0), arrayOf(clientCert) ) } // 初始化客户端凭证管理器 val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { init(clientKeyStore, CharArray(0)) } // 复用系统默认信任库,和curl默认行为一致,无需额外导入服务端证书 val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { init(null as KeyStore?) } val defaultTrustManager = trustManagerFactory.trustManagers.first() as X509TrustManager // 构造TLS上下文 val sslContext = SSLContext.getInstance("TLS").apply { init(keyManagerFactory.keyManagers, trustManagerFactory.trustManagers, null) } // 初始化OkHttp引擎的Ktor客户端 return HttpClient(OkHttp) { engine { config { sslSocketFactory(sslContext.socketFactory, defaultTrustManager) } } } } // POST请求调用示例 suspend fun callTargetEndpoint() { val mtlsClient = buildMtlsClient() val resp = mtlsClient.post("https://someurl.com/my-endpoint") { contentType(ContentType.Application.Json) // 替换成实际的请求体内容 setBody("""{"testParam":"testValue"}""") } // 处理响应 println(resp.bodyAsText()) mtlsClient.close() }
注意事项
- 私钥文件路径、证书文件路径请替换成你项目中的实际存储路径,生产环境建议把凭证放到资源目录读取,不要硬编码磁盘路径。
- 如果私钥设置了访问密码,把代码里的
CharArray(0)替换成实际密码对应的字符数组即可。 - 不要使用“信任所有证书”的自定义TrustManager实现,会跳过服务端证书校验带来中间人攻击风险,上述配置已经和curl的默认校验逻辑完全对齐。
内容的提问来源于stack exchange,提问作者Coyado
相关产品推荐
相关产品推荐

