如何在Ktor(Netty)服务器中自定义trustManager或sslContext?
问题背景
我在基于Netty的Ktor服务器中配置了mTLS,但需要添加自定义TLS逻辑(比如忽略客户端证书的有效性),却找不到修改trustManager或sslContext的方法。
当前已通过keystore配置mTLS的代码:
val environment = applicationEngineEnvironment { connector { port = 8080 } sslConnector( keyStore = keystore, keyAlias = "selfsigned", keyStorePassword = { "".toCharArray() }, privateKeyPassword = { "changeit".toCharArray() } ) { port = 8443 trustStore = clientkeystore } module { routing { get("/204") { call.response.status(HttpStatusCode.NoContent) } } } } embeddedServer(Netty, environment).start(true)
查看Ktor源码发现,sslContext和trustManager是在NettyChannelInitializer中初始化的(核心代码片段):
public class NettyChannelInitializer( private val enginePipeline: EnginePipeline, private val environment: ApplicationEngineEnvironment, private val callEventGroup: EventExecutorGroup, private val engineContext: CoroutineContext, private val userContext: CoroutineContext, private val connector: EngineConnectorConfig, private val requestQueueLimit: Int, private val runningLimit: Int, private val responseWriteTimeout: Int, private val requestReadTimeout: Int, private val httpServerCodec: () -> HttpServerCodec, private val channelPipelineConfig: ChannelPipeline.() -> Unit ) : ChannelInitializer<SocketChannel>() { private var sslContext: SslContext? = null init { if (connector is EngineSSLConnectorConfig) { // It is better but netty-openssl doesn't support it // val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) // kmf.init(ktorConnector.keyStore, password) // password.fill('\u0000') @Suppress("UNCHECKED_CAST") val chain1 = connector.keyStore.getCertificateChain(connector.keyAlias).toList() as List<X509Certificate> val certs = chain1.toList().toTypedArray() val password = connector.privateKeyPassword() val pk = connector.keyStore.getKey(connector.keyAlias, password) as PrivateKey password.fill('\u0000') sslContext = SslContextBuilder.forServer(pk, *certs).apply { if (alpnProvider != null) { sslProvider(alpnProvider) ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) applicationProtocolConfig( ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1 ) ) } connector.trustManagerFactory()?.let { this.trustManager(it) } } .build() } } }
我尝试过使用childHandler但从未被调用,代码如下:
embeddedServer(Netty, port = 8443, configure = { configureBootstrap = { val certsChain = keystore2.getCertificateChain("selfsigned").toList() as List<X509Certificate> val certs = certsChain.toTypedArray() val password = "changeit".toCharArray() val privateKey = keystore2.getKey("selfsigned", password) as PrivateKey val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) .also { it.init(keystore2) } childHandler(object : ChannelInitializer<SocketChannel>() { override fun initChannel(ch: SocketChannel) { println("initChannel") val sslContext = SslContextBuilder.forServer(privateKey, *certs) .trustManager(trustManagerFactory) .build() val sslEngine = sslContext.newEngine(ch.alloc()).apply { useClientMode = false needClientAuth = false } ch.pipeline().addLast(SslHandler(sslEngine)) } }) } }, module = { routing { get("/204") { call.response.status(HttpStatusCode.NoContent) } } }).start(wait = true)
请问是否可以通过channelPipelineConfig或configureBootstrap来配置该逻辑?还是需要创建自定义Netty引擎?
解决方案
方法1:自定义TrustManager实现忽略客户端证书验证
直接创建一个信任所有客户端证书的X509TrustManager,通过SslContextBuilder构建自定义sslContext后传入Ktor的sslConnector,这种方式最直接:
// 创建信任所有证书的TrustManager val trustAllCerts = arrayOf<X509TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() }) // 构建自定义SslContext val sslContext = SslContextBuilder.forServer( keystore.getKey("selfsigned", "changeit".toCharArray()) as PrivateKey, *keystore.getCertificateChain("selfsigned").toList().toTypedArray() ) .trustManager(trustAllCerts[0]) .clientAuth(ClientAuth.REQUIRE) // 保持要求客户端证书,但跳过验证;不需要的话可以设为NONE .build() val environment = applicationEngineEnvironment { connector { port = 8080 } sslConnector(sslContext = sslContext) { port = 8443 } module { routing { get("/204") { call.response.status(HttpStatusCode.NoContent) } } } } embeddedServer(Netty, environment).start(true)
方法2:通过channelPipelineConfig修改已有SslHandler
Ktor的Netty引擎支持通过channelPipelineConfig扩展点修改管道,直接修改已初始化的SslHandler配置:
val environment = applicationEngineEnvironment { connector { port = 8080 } sslConnector( keyStore = keystore, keyAlias = "selfsigned", keyStorePassword = { "".toCharArray() }, privateKeyPassword = { "changeit".toCharArray() } ) { port = 8443 trustStore = clientkeystore } module { routing { get("/204") { call.response.status(HttpStatusCode.NoContent) } } } } embeddedServer(Netty, environment) { channelPipelineConfig = { val sslHandler = this.get(SslHandler::class.java) sslHandler.engine.apply { // 设置自定义TrustManager跳过验证 val trustAllCerts = arrayOf<X509TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() }) val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustAllCerts, SecureRandom()) this.sslContext = sslContext // 可选:如果不需要强制客户端证书,关闭要求 needClientAuth = false } } }.start(true)
方法3:自定义Netty引擎(进阶场景)
如果需要完全控制TLS初始化逻辑,可以自定义NettyApplicationEngine,重写NettyChannelInitializer的初始化逻辑:
class CustomNettyEngine(environment: ApplicationEngineEnvironment, configure: NettyApplicationEngine.Configuration.() -> Unit = {}) : NettyApplicationEngine(environment, configure) { override fun createChannelInitializer(): ChannelInitializer<SocketChannel> { return object : NettyChannelInitializer( enginePipeline, environment, callEventGroup, engineContext, userContext, connectors.first(), configuration.requestQueueLimit, configuration.runningLimit, configuration.responseWriteTimeout, configuration.requestReadTimeout, { HttpServerCodec() }, configuration.channelPipelineConfig ) { override fun initChannel(ch: SocketChannel) { // 先执行父类的初始化逻辑 super.initChannel(ch) // 修改已有的SslHandler val sslHandler = ch.pipeline().get(SslHandler::class.java) sslHandler.engine.apply { val trustAllCerts = arrayOf<X509TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {} override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() }) val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustAllCerts, SecureRandom()) this.sslContext = sslContext } } } } } // 使用自定义引擎启动服务器 CustomNettyEngine(environment).start(true)
关于之前childHandler未生效的原因
Ktor的Netty引擎内部已经设置了childHandler(即NettyChannelInitializer),你在configureBootstrap中手动设置的childHandler会被内部逻辑覆盖,因此不会被调用。必须通过Ktor提供的官方扩展点(如channelPipelineConfig)或自定义引擎来修改管道逻辑。
内容的提问来源于stack exchange,提问作者hoxewe7277

