如何阻止HTTP请求访问KTor HTTPS服务器及处理Netty TLS异常?
我用KTor搭建的HTTPS服务器和Android客户端交互正常,但总有机器人往HTTPS端口发HTTP请求,导致Netty抛出NotSslRecordException并打印大量WARN级别的堆栈日志,示例日志如下:
23-11-2023 22:51:39.249 [eventLoopGroupProxy-3-1] [WARN ] i.n.h.s.ApplicationProtocolNegotiationHandler - [id: 0x58e8b7a9, L:0.0.0.0/0.0.0.0:1234 ! R:/321.567.33.980:18765] TLS handshake failed:
io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 474554202f7465737420485454502f312e310d0a486f73743a207777772e636172726f74736f6674676174657761792e636f6d3a343234320d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b2078363429204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3131392e302e302e30205361666172692f3533372e33360d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f617669662c696d6167652f776562702c696d6167652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d302e370d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a20656e2d55532c656e3b713d302e390d0a0d0a
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1225)
...(省略后续堆栈)
一、在Netty层面拦截HTTP请求
直接在Netty的ChannelPipeline最前面加一个处理器,识别HTTP请求的特征并直接关闭连接,避免走到TLS握手环节触发异常。
在KTor中可以通过自定义Netty服务器配置实现:
import io.ktor.server.netty.* import io.netty.channel.* import io.netty.channel.socket.SocketChannel import io.netty.buffer.ByteBuf import java.nio.charset.StandardCharsets fun main() { embeddedServer(Netty, port = 0, sslPort = 4242) { // 这里配置你的路由逻辑 }.apply { // 获取Netty的ServerBootstrap并添加前置处理器 val serverBootstrap = (this as NettyApplicationEngine).serverBootstrap serverBootstrap.handler(object : ChannelInitializer<SocketChannel>() { override fun initChannel(ch: SocketChannel) { ch.pipeline().addFirst("http-blocker", object : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is ByteBuf) { // 读取前3个字节判断是否为HTTP请求方法前缀(GET/POS/PUT等) if (msg.readableBytes() >= 3) { val prefixBytes = ByteArray(3) msg.getBytes(msg.readerIndex(), prefixBytes) val prefix = String(prefixBytes, StandardCharsets.US_ASCII) if (prefix in listOf("GET", "POS", "PUT", "DEL", "HEA", "OPT")) { // 直接关闭连接,不处理后续逻辑 ctx.close() return } } } // 不是HTTP请求,传递给下一个处理器(TLS握手) ctx.fireChannelRead(msg) } }) } }) }.start(wait = true) }
二、抑制Netty的WARN日志输出
如果不想修改代码,也可以通过日志框架(以Logback为例)过滤掉特定的WARN日志,避免打印冗长的堆栈:
在logback.xml中添加如下配置:
<configuration> <!-- 其他配置 --> <logger name="io.netty.handler.ssl.ApplicationProtocolNegotiationHandler" level="WARN"> <filter class="ch.qos.logback.classic.filter.LevelMatchFilter"> <level>WARN</level> </filter> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator> <!-- 只过滤包含TLS握手失败且异常为NotSslRecordException的日志 --> <expression>message contains "TLS handshake failed" && throwable instanceof io.netty.handler.ssl.NotSslRecordException</expression> </evaluator> <onMatch>DENY</onMatch> <onMismatch>NEUTRAL</onMatch> </filter> </logger> </configuration>
三、通用最佳实践
- 前置反向代理:用Nginx等反向代理接收请求,Nginx会自动拒绝HTTPS端口的HTTP请求,同时还能做IP黑名单、速率限制等防护。
- 端口分离:如果需要支持HTTP,单独用一个端口,然后把HTTP请求重定向到HTTPS;不需要的话直接关闭HTTP端口。
- IP黑名单:收集频繁发送无效请求的IP,通过防火墙或应用层规则拉黑。
- 速率限制:在KTor中配置速率限制插件,限制单个IP的请求频率,减少机器人的干扰。
内容的提问来源于stack exchange,提问作者Cool_Coder

