WebFlux + R2DBC:如何防范恶意REST客户端慢响应消耗式DDoS攻击
你担心的客户端慢消费或断开连接导致数据库连接被长期占用的问题确实存在,但可以通过以下几种机制缓解:
给响应流添加超时控制
在返回的Flux上添加timeout操作符,当客户端超过指定时间未消费数据时,主动终止整个流,触发R2DBC释放数据库连接。示例代码:@GetMapping("items") public Flux<Item> findAll() { return repository.findAll() .timeout(Duration.ofSeconds(30)); // 30秒无消费则终止流 }这个操作符会监听流的消费状态,一旦超时就抛出
TimeoutException,上游的数据库查询会被中断,连接自动归还连接池。配置连接池的兜底回收策略
不管业务层的流处理是否正常,给R2DBC连接池配置空闲超时和最大生命周期,让连接池主动回收异常占用的连接。以常用的r2dbc-pool为例,在配置文件中添加:spring.r2dbc.pool.max-idle-time=PT30S spring.r2dbc.pool.max-life-time=PT5M这样即使出现信号传递异常导致连接未被正常释放,连接池也会在连接空闲30秒或存活5分钟后强制回收,避免连接池耗尽。
设置Web服务器的读写超时
Spring 6 WebFlux虽然默认未配置,但可以通过服务器层面的参数限制读写超时,Netty服务器的配置示例:@Bean public NettyServerCustomizer nettyServerCustomizer() { return server -> server .childOption(ChannelOption.SO_TIMEOUT, 30000) .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000); }或者用Spring Boot配置文件简化:
spring.netty.server.read-timeout=30s spring.netty.server.write-timeout=30s当客户端长时间无读写操作时,服务器会主动关闭套接字,触发响应流的
onCancel信号,进而终止数据库查询并释放连接。依赖响应式背压机制
WebFlux和R2DBC都原生支持Reactor的背压机制,当客户端消费速度慢时,上游的数据库查询会自动暂停,不会持续占用连接发送数据。只要你的repository.findAll()返回的是支持背压的Flux(R2DBC的查询默认满足),就能避免连接被无意义占用。
另外需要纠正一个认知:正常情况下,客户端断开连接时,WebFlux会触发响应流的onCancel信号,Reactor会立即终止上游的数据库查询,R2DBC会同步释放连接。只有在网络异常导致信号无法及时传递的极端场景下,才需要上面的超时和连接池配置来兜底。
内容的提问来源于stack exchange,提问作者Sokolof

