Spring Cloud Gateway 恶意长连接防护:客户端建立连接不发数据?空闲超时强制断开,释放线程资源!
做过网关开发的同学肯定都遇到过这个问题:恶意用户或者异常客户端建立 TCP 连接后,什么请求都不发送,就静静保持着连接不走。这样会导致什么后果呢?
我之前就遇到过这样一个案例:线上网关突然出现大量连接超时,排查后发现是有人在用脚本模拟"占坑"攻击——同时发起几千个连接,但每个连接都只发送一个请求后就再也不发数据了。这些空闲连接占用着 Netty 的 Worker 线程和内存资源,导致正常请求无法处理。
今天我们就来聊聊 Spring Cloud Gateway 的恶意长连接防护方案,让你的网关不再被"僵尸连接"拖垮。
恶意长连接的危害
1. 线程资源耗尽
Spring Cloud Gateway 基于 Netty 实现,Netty 的工作模型是这样的:
Netty 线程模型:
Boss 线程(1个) → 接受连接 → 分配给 Worker 线程池
Worker 线程池(N个) → 处理读写事件 → 如果有耗时操作会阻塞线程
问题场景:恶意长连接占用 Worker 线程
- 1000 个空闲连接
- 每个占用一个 Worker 线程
- 正常请求排队等待可用线程
- 响应超时
2. 内存资源泄漏
每个 TCP 连接都会占用一定的内存:
连接内存占用:
1. Socket 缓冲区:默认 16KB * 2(接收+发送)
2. NIO Channel 对象:约 2KB
3. Netty ChannelHandlerContext:约 1KB
4. 请求/响应缓冲区:初始 8KB,可扩展
单个连接 ≈ 40KB+
10000 个恶意连接 ≈ 400MB+ 内存占用
3. 并发能力下降
正常情况下,网关的并发能力取决于 Worker 线程数量:
Netty 默认配置:
- Worker 线程数 = CPU 核心数 * 2
比如 8 核 CPU:
- Worker 线程 = 16
- 同时处理的连接 ≈ 16(因为每个连接可能占用多个事件循环)
如果被恶意长连接占满:
- 正常请求无线程可用
- 请求堆积
- 最终导致服务不可用
解决方案:空闲超时 + 强制断开
1. 核心设计思想
我们的方案核心是三个关键机制:
- 空闲检测:检测连接多长时间没有数据传输
- 超时断开:超过设定时间后主动关闭连接
- 资源清理:确保连接关闭后释放所有资源
架构图如下:
┌─────────────────────────────────────────────────────────────────┐
│ 恶意长连接防护架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 客户端 │───→│ Netty Server │───→│ Idle Handler │ │
│ │ │ │ │ │ (空闲检测) │ │
│ └──────────┘ └────────────────┘ └────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ │◀───│ Channel │◀───│ 超时断开 │ │
│ │ │ │ (连接管理) │ │ (force close) │ │
│ └──────────┘ └────────────────┘ └────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ 资源清理 │ │
│ │ (释放线程/内存) │ │
│ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 空闲检测的原理
Netty 提供了 IdleStateHandler 来检测连接空闲:
IdleStateHandler 参数说明:
1. readerIdleTime:读取空闲时间(没有收到数据)
2. writerIdleTime:写入空闲时间(没有发送数据)
3. allIdleTime:任意方向空闲时间
检测逻辑:
when connection.isActive():
lastReadTime = now()
if now() - lastReadTime > readerIdleTime:
trigger(READER_IDLE)
if now() - lastWriteTime > writerIdleTime:
trigger(WRITER_IDLE)
if now() - max(lastReadTime, lastWriteTime) > allIdleTime:
trigger(ALL_IDLE)
3. 空闲处理器实现
// 空闲连接处理
class IdleDisconnectHandler extends ChannelInboundHandlerAdapter {
// 读取空闲时间(秒)
private final int readerIdleTimeSeconds;
// 强制断开连接
private final boolean forceClose;
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleEvent = (IdleStateEvent) evt;
String idleType = switch (idleEvent.state()) {
case READER_IDLE -> "读取空闲";
case WRITER_IDLE -> "写入空闲";
case ALL_IDLE -> "全部空闲";
};
log.warn("检测到{}连接, channel={}, idleTime={}s",
idleType, ctx.channel(), readerIdleTimeSeconds);
if (forceClose) {
// 强制关闭连接
ctx.close();
log.info("已强制关闭空闲连接: {}", ctx.channel());
}
}
}
}
4. Spring Cloud Gateway 配置
# 网关空闲超时配置
spring:
cloud:
gateway:
httpclient:
response-timeout: 30s
connect-timeout: 10s
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
server:
port: 8080
# Netty 配置
reactor:
netty:
ioWorkerCount: 16 # Worker 线程数
connectionTimeout: 10s # 连接超时
5. 自定义过滤器实现
// 网关空闲检测过滤器
class IdleTimeoutFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpServerVersion version = HttpServerVersion.forVersion(1, 1);
return chain.filter(exchange)
.doOnCancel(() -> {
log.debug("连接已取消: {}", exchange.getRequest().getURI());
})
.doOnError(error -> {
log.error("连接异常: {}", exchange.getRequest().getURI(), error);
});
}
}
最佳实践与配置建议
1. 合理设置超时时间
超时时间配置原则:
1. 读取空闲(readerIdleTime):
- 建议:30-60 秒
- 太短:正常请求可能被误杀(如弱网环境)
- 太长:恶意连接占用资源时间过长
2. 写入空闲(writerIdleTime):
- 建议:60-120 秒
- 用于检测"只建立不发送请求"的连接
3. 全部空闲(allIdleTime):
- 建议:60 秒
- 综合检测读写两个方向
2. 分级防护策略
分级防护配置:
轻度防护(正常业务):
- readerIdleTime: 60s
- 仅警告,不断开
中度防护(可疑行为):
- readerIdleTime: 30s
- 发送心跳探测
- 如果无响应则断开
重度防护(攻击行为):
- readerIdleTime: 10s
- 直接断开
- 记录日志并触发告警
3. 监控与告警
需要监控的指标:
1. 当前空闲连接数
2. 每分钟断开的空闲连接数
3. 被强制关闭的连接数
4. 断开连接的平均空闲时间
告警规则:
- 每分钟断开连接数 > 100:可能存在攻击
- 空闲连接占比 > 30%:资源配置异常
- 单个 IP 空闲连接数 > 50:可疑 IP
4. 防御纵深
多层防护策略:
第一层:网关层(Netty IdleHandler)
- 快速检测,快速断开
- 保护下游服务
第二层:负载均衡层(Nginx/LVS)
- 连接数限制
- 单 IP 限流
第三层:防火墙层
- SYN Flood 防护
- 连接速率限制
第四层:应用层
- 请求频率限制
- 恶意行为识别
5. 日志埋点
日志规范:
1. 检测到空闲连接:
level=INFO, msg="检测到空闲连接", channelId=xxx, idleTime=60s
2. 发送心跳探测:
level=INFO, msg="发送心跳探测", channelId=xxx, remoteIp=xxx
3. 强制关闭连接:
level=WARN, msg="强制关闭空闲连接", channelId=xxx, idleTime=60s, reason=idle_timeout
4. 连接异常:
level=ERROR, msg="连接异常", channelId=xxx, error=xxx
配置参数建议
# 完整的网关安全配置
spring:
cloud:
gateway:
httpclient:
response-timeout: 30s
connect-timeout: 10s
pool:
max-idle-time: 60s # 空闲连接最大存活时间
min-idle-time: 5s # 最小空闲时间
max-connections: 1000 # 最大连接数
max-connections-per-route: 100 # 单路由最大连接
server:
port: 8080
netty:
connection-timeout: 60s # 连接超时
management:
endpoints:
web:
exposure:
include: health,info,metrics
metrics:
tags:
application: ${spring.application.name}
logging:
level:
reactor.netty: INFO
com.example.gateway: DEBUG
效果对比
| 方案 | 防护效果 | 资源占用 | 误杀率 | 适用场景 |
|---|---|---|---|---|
| 无防护 | ❌ | 高 | 0% | 测试环境 |
| 简单超时 | ⚠️ | 中 | 高 | 简单场景 |
| IdleHandler | ✅ | 低 | 低 | 生产环境 |
| 分级防护 | ✅ | 低 | 极低 | 高安全要求 |
总结
恶意长连接防护的核心原则:
- 及时检测:使用 Netty IdleHandler 检测空闲连接
- 合理超时:根据业务场景设置合适的超时时间
- 强制断开:检测到恶意行为后立即关闭连接
- 资源清理:确保断开后释放所有资源
- 分级防护:不同级别采用不同的防护策略
记住:连接是资源,不是越多越好。通过合理的空闲超时配置,可以让网关自动清理"僵尸连接",保护宝贵的线程和内存资源。
源码获取
文章已同步至小程序博客栏目,需要源码的请关注小程序博客。
公众号:服务端技术精选
小程序码:
标题:Spring Cloud Gateway 恶意长连接防护:客户端建立连接不发数据?空闲超时强制断开,释放线程资源!
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/05/26/1779205573694.html
公众号:服务端技术精选
评论
0 评论