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

如何在Ktor(Netty)服务器中自定义trustManager或sslContext?

在基于Netty的Ktor服务器中自定义TLS连接逻辑(忽略客户端证书有效性)

问题背景

我在基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 20:35:55