JAVA实现动态IP黑名单过滤

news/2025/4/2 7:23:00/

 一些恶意用户(可能是黑客、爬虫、DDoS 攻击者)可能频繁请求服务器资源,导致资源占用过高。因此需要一定的手段实时阻止可疑或恶意的用户,减少攻击风险。

通过 IP 封禁,可以有效拉黑攻击者,防止资源被滥用,保障合法用户的正常访问。对于我们的需求,不让拉进黑名单的IP 访问任何接口

Sentinel本身支持请求来源的黑白名单判断,但默认是对应用级别进行判断,需要改造来源的获取方式为获取请求客户端的 IP,可参考这篇文章自定义来源。但是需要一定成本的

需求分析

使用Nacos是更轻量的动态 IP 黑白名单过滤的常用设计和实现方法。主要考虑以下几点:

IP 黑名单存储在哪里?

一般 IP 黑名单是动态增加的、需要持久化保存。常见的持久化方式包括数据库、配置文件或分布式存储系统(如 Redis),可以根据需要选择。

如何便捷地动态修改 IP 黑名单?

为了方便动态修改 IP 黑名单,所以这里考虑将 配置统一放入 配置中心,通过配置中心的管理页面,开发人员可以便捷地动态修改黑名单规则。Java 项目中,常用的配置中心是 Nacos

黑白名单的判断逻辑应在哪里处理?

黑白名单逻辑通常部署在高性能的网关或 CDN 上,能更早地拦截非法请求,减轻后端压力。小型项目中,也可直接在应用程序的过滤器中处理

使用何种数据结构保存黑名单? 如何快速匹配用户请求的 IP 是否在黑名单中?

为了高效判断每个用户请求的 IP 是否在黑名单中,首先建议将IP 黑名单从持久化存储同步到本地缓存中,避免频繁查询远程数据源。

对于大流量刷题网站,大规模黑名单使用 布隆过滤器 来存储和过滤黑名单是比较好的,可以节约内存空间、提高检测效率。

最终方案如下

  1. 使用Nacos配置中心存储和管理 IP 黑名单

  2. 后端服务利用Web过滤器判断每个用户请求的 IP

  3. 后端服务利用布隆过滤器过滤 IP 黑名单

布隆过滤器

布隆过滤器是一种高效的概率数据结构,常用于检测一个元素是否在一个集合中,可以有效减少数据库的查询次数,解决缓存穿透等问题

布隆过滤器是由一个位数组k个独立的哈希函数组成。

  • 添加元素时,通过k个哈希函数将元素映射到位数组的k个位置上,将这些位置设置为 1。

  • 查询元素是否存在时,同样计算k个位置

    • 如果所有位置都是1,则说明元素可能存在(因为可能多个不同值通过哈希函数映射到同一位

    • 只要有一个位置为 0,就可以确定元素一定不存在。

其次布隆过滤器不能删除元素,因为可能多个元素映射到同一个位置,修改了这个位置,就会导致其它元素判断错误

Bloom Filter 的误判率与以下因素有关

  • 位数组的大小: 位数组越大,误判率越低,但空间开销会增大。(值会更离散)

  • 哈希函数的个数: 哈希函数越多,误判率越低,但计算成本会增加。(Hash 一次冲突,那我就多 Hash 几次,减少冲突概率)

  • 元素数量: 存入的元素越多,误判率会增加。

布隆过滤器适用场景

布隆过滤器一般都在海量数据判断场景,且可以允许误判。

  1. redis结合bitmap使用,解决缓存穿透的问题

  2. 黑名单校验,识别垃圾邮件

比如: 识别垃圾邮件,把所有黑名单地址都放在布隆过滤器中,在收到邮件时,判断邮件地址是否在布隆过滤器中即可。

Nacos配置中心

官网:Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网 一个更易于构建云原生应用的动态服务发现,并且自动配置持久化,配置管理和服务管理平台

Nacos=Eureka+Config+Bus  比 SpringCloud Consul更为强大,并且自带负载均衡功能

下载安装运行

Nacos 快速开始 | Nacos 官网

https://github.com/alibaba/nacos/releases

解压直接运行bin目录下输入cmd命令
startup.cmd -m standalone      #代表单机非集群模式运行

运行成功后直接访问 http://localhost:8848/nacos/index.html

默认账号密码都是 nacos

后端开发

运行Nacos,创建配置,表示黑名单

blackIpList:- "1.1.1.1"- "2.2.2.2"

项目引入依赖

Nacos 融合 Spring Boot,成为注册配置中心 | Nacos 官网

<!--版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x版本-->
<dependency><groupId>com.alibaba.boot</groupId><artifactId>nacos-config-spring-boot-starter</artifactId><version>0.2.12</version>
</dependency>
# 配置中心
nacos:config:server-addr: 127.0.0.1:8848  # nacos 地址bootstrap:enable: true         # 预加载data-id: praxisAI    # 控制台填写的 Data IDgroup: DEFAULT_GROUP  # 控制台填写的 grouptype: yaml            # 选择的文件格式auto-refresh: true    # 开启自动刷新
创建黑名单过滤工具类

新建blackfilter包,黑名单过滤相关的代码都放到该包下,模块化。参考文章,如果是分布式,还可以考虑 Redisson。可以用 Hutool 或 Guava 库自带的 bloomfilter,此处由于项目已经使用了 Hutool 工具库,就用其自带的BitMapBloomFilter即可。

java">/*** 黑名单过滤工具类*/
@Slf4j
public class BlackIpUtils {//静态的布隆过滤器,存IP黑名单,默认大小1000private static BitMapBloomFilter bloomFilter;
​// 判断 ip 是否在黑名单里public static boolean isBlackIp(String ip) {//true 表示可能在黑名单里//false 一定不在黑名单里return bloomFilter.contains(ip);}
​//获取 nacos 上的黑名单,添加到布隆过滤器中public static void rebuildBlackIp(String configInfo) {//判断配置信息是否为空if (StrUtil.isBlank(configInfo)) {configInfo = "{}";}// 解析 yaml 文件Yaml yaml = new Yaml();//将传入的配置信息解析成 Map 对象Map map = yaml.loadAs(configInfo, Map.class);
​// 获取配置中的 IP 黑名单List<String> blackIpList = (List<String>) map.get("blackIpList");
​// 加锁防止多线程修改布隆过滤器synchronized (BlackIpUtils.class) {//黑名单不为空if (CollUtil.isNotEmpty(blackIpList)) {// 注意构造参数的设置,容量越大,误判率越低BitMapBloomFilter bitMapBloomFilter = new BitMapBloomFilter(1000);//遍历黑名单,将黑名单添加到布隆过滤器中for (String blackIp : blackIpList) {bitMapBloomFilter.add(blackIp);}bloomFilter = bitMapBloomFilter;}//黑名单为空else {bloomFilter = new BitMapBloomFilter(1000);}}}
}

创建Nacos配置监听类

@RefreshScope配置动态刷新,当Nacos配置变化时,可以触发工具类的重新初始化,更新黑名单,但是仅仅只是更新到一个列表里,布隆过滤器内部是由代码控制的

所以这里直接通过 Nacos 控制台获取示例代码,在blackfilter包中新增监听器代码

整体流程

  1. Spring 容器启动后,调用 afterPropertiesSet 方法。

  2. Nacos 获取当前的黑名单配置,并将其加载到布隆过滤器中。

  3. 并且注册一个监听器,用于监听 Nacos 配置的变化。

  4. 处理配置变化

    • Nacos 配置发生变化时,监听器的 receiveConfigInfo 方法会被触发。

    • 新的配置内容会被传递给 BlackIpUtils.rebuildBlackIp 方法,重新构建布隆过滤器。

  5. 使用自定义线程池异步处理nacos配置变化,避免阻塞主线程。

java">/*** Nacos 监听器*/
@Slf4j
@Component
//表示该类会在 Spring 容器初始化完成后自动调用 afterPropertiesSet 方法
public class NacosListener implements InitializingBean {@NacosInjectedprivate ConfigService configService;  //与 nacos 配置中心交互@Value("${nacos.config.data-id}")private String dataId;@Value("${nacos.config.group}")private String group;@Overridepublic void afterPropertiesSet() throws Exception {log.info("nacos 监听器启动");//从 Nacos 获取指定 dataId 和 group 的配置内容String config = configService.getConfigAndSignListener(dataId, group, 3000L,new Listener() { //注册一个nacos监听器,监听配置信息的变化//定义线程工程final ThreadFactory threadFactory = new ThreadFactory() {//原子类,用于生成线程名称private final AtomicInteger poolNumber = new AtomicInteger(1);@Overridepublic Thread newThread(@NotNull Runnable r) {Thread thread = new Thread(r);thread.setName("refresh-ThreadPool" + poolNumber.getAndIncrement());return thread;}};//自定义线程池:使用固定大小的线程池,传入线程工厂,用于异步处理配置变化的逻辑final ExecutorService executorService = Executors.newFixedThreadPool(1, threadFactory);// 通过线程池异步处理黑名单变化的逻辑@Overridepublic Executor getExecutor() {return executorService;}// 监听后续黑名单变化//当nacos配置中心配置信息发生变化时,会接收新的配置configInfo加载到布隆过滤器中@Overridepublic void receiveConfigInfo(String configInfo) {log.info("监听到配置信息变化:{}", configInfo);//将新的黑名单配置加载到布隆过滤器中BlackIpUtils.rebuildBlackIp(configInfo);}});// 初始化黑名单BlackIpUtils.rebuildBlackIp(config);}
}
创建黑名单过滤

黑名单应该对所有请求生效( 不止是 Controler 的接口 ),所以基于WebFilter实现而不是 AOP 切面。WebFilter的优先级高于@Aspect切面,因为它在整个 Web 请求生命周期中更早进行处理。

请求进入时的顺序:

  1. 首先,WebFilter拦截 HTTP 请求,并可以根据逻辑决定是否继续执行请求。

  2. 如果请求到过滤器并进入 Spring 的 Bean( 例如 Controller层 ),此时AOP切面生效,对匹配的 Bean 方法进行拦截

  3. 如果 @Aspect 没有阻止执行,最终请求到达 @Controller 或 @RestController 的方法

java">/*** 全局 IP 黑名单过滤请求拦截器*/
//声明一个过滤器,拦截所有的 HTTP 请求
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter")
public class BlackIpFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//获取客户端IP地址String ipAddress = NetUtils.getIpAddress((HttpServletRequest) servletRequest);//判断IP地址是否在黑名单中(布隆过滤器中)if (BlackIpUtils.isBlackIp(ipAddress)) {//如果在,则返回错误信息servletResponse.setContentType("text/json;charset=UTF-8");servletResponse.getWriter().write("{\"errorCode\":\"-1\",\"errorMsg\":\"黑名单IP,禁止访问\"}");return;}//放行请求filterChain.doFilter(servletRequest, servletResponse);}
}

最后需要在需要在启动类上加上@ServletComponentScan,这样过滤器才会被扫描到。

@SpringBootApplication
@MapperScan("com.zr.praxisai.mapper")
@EnableScheduling
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)  //开启SpringAOP功能
@ServletComponentScan  //扫描原生的 Servlet 组件(如过滤器、监听器、Servlet)
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}
}
测试

通过 Nacos 控制台修改配置,本地测试的话直接加入本机 IP 即可,Nacos控制台可以看到改动记录和历史版本

blackIpList:- "1.1.1.1"- "2.2.2.2"- "0:0:0:0:0:0:0:1"

通过修改Nacos配置查看后端监听情况

通过Swagger测试发现直接打不开了


http://www.ppmy.cn/news/1584577.html

相关文章

实战 | 基于 SpringBoot + UniApp 打造国际版打车系统:架构设计与性能优化全解析

✅ 一、引言&#xff1a;国际版打车系统的技术挑战 随着共享出行在全球范围内的快速发展&#xff0c;跨国打车平台如 Uber、Lyft 和 DiDi 等纷纷崛起。开发一套国际版打车系统&#xff0c;不仅要满足国内需求&#xff0c;还需要应对以下技术挑战&#xff1a; &#x1f30d; 多…

Altium Designer 24 PCB编辑器[设计]栏找不到[规则]选项而只有[Constraints Manager]选项

Altium Designer 24 PCB编辑器[设计]栏找不到[规则]选项而只有[Constraints Manager]选项 问题描述问题原因解决方法 问题描述 在使用 Altium Designer 24 的PCB编辑器时&#xff0c;发现有的PCB文件的【设计】栏下有【规则】这个选项&#xff0c;有的PCB文件的【设计】栏下没…

蓝桥杯 之 LCA算法

文章目录 习题1483.树节点的第K个祖先拓展&#xff1a;LCA LCA问题&#xff0c;就是最近公共祖先的问题 习题 1483.树节点的第K个祖先 1483.树节点的第K个祖先 普通的做法&#xff0c;当然是一个个往上面搜索&#xff0c;但是这样的话时间复杂度是o(k),那么能不能每次求解的是…

JVM如何判断一个对象可以被回收

在 Java 中&#xff0c;JVM 使用 垃圾回收器 (GC) 来自动管理内存。JVM 判断一个对象是否可以被回收的主要依据是 对象是否可达。具体来说&#xff0c;如果某个对象不再被任何可达的引用所引用&#xff0c;那么这个对象就可以被认为是 垃圾&#xff0c;可以被回收。 判断一个对…

win 远程 ubuntu 服务器 安装图形界面

远程结果&#xff1a;无法使用docker环境使用此方法 注意要写IP和:数字 在 ubuntu 服务器上安装如下&#xff1a; # 安装 sudo apt-get install tightvncserver # 卸载 sudo apt purge tightvncserver sudo apt autoremove#安装缺失的字体包&#xff1a; sudo apt update s…

【自学笔记】PHP语言基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1. PHP 简介2. PHP 环境搭建3. 基本语法变量与常量数据类型运算符 4. 控制结构条件语句循环语句 5. 函数函数定义与调用作用域 6. 数组7. 字符串8. 表单处理9. 会话…

Android HAL 架构详解,底层开发不再难

目录 HAL 基础概念 HAL 是个啥? 为啥要有 HAL? HAL 在系统中的位置 HAL 工作原理 抽象接口:硬件的 “通用语言” 接口的设计思路 核心结构体 版本与兼容性 实例:相机 HAL 接口 模块加载:动态链接的魔法 加载步骤 优化策略 实例:加载音频 HAL 通信机制:HAL…

数字电子技术基础(三十六)——利用Multisim软件实现3线-8线译码器

目录 1 手动方式实现3线-8线译码器 2 使用字选择器实现3线-8线译码器 现在尝试利用Multisim软件来实现3线-8线译码器。本实验目的是验证74LS138的基本功能&#xff0c;简单来说就是“N中选1”。 实验设计&#xff1a; &#xff08;1&#xff09;使能信号&#xff1a;时&am…