Nacos实现IP动态黑白名单过滤

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

本次练习使用到的是Nacos配合布隆过滤器实现动态IP黑白名单过滤

文章目录

目录

文章目录

一、IP黑白名单是什么?

二、使用步骤

1.使用Nacos

1.通过Nacos添加配置

2.引入依赖

2.使用

1.定义一个获取IP的方法

2.创建黑名单过滤工具类

3.创建Nacos配置监听类

4.创建黑白名单过滤器

总结


一、IP黑白名单是什么?

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

通过 IP 封禁,可以有效拉黑攻击者,防止资源被滥用,保障合法用户的正常访问。

对于我们的需求,不让拉进黑名单的 IP 访问任何接口。

二、使用步骤

1.使用Nacos

首先就是下载Nacos

c 复制代码
通过网盘分享的文件:nacos
链接: https://pan.baidu.com/s/12-9UA6hUSlEeyuKVfNPkJw?pwd=fr2z 提取码: fr2z 
--来自百度网盘超级会员v6的分享

拿到文件夹内容是

打开bin目录

java 复制代码
//运行命令行
startup.sh -m standalone

看到这个界面就代表着Nacos运行成功了

1.通过Nacos添加配置

访问:http://127.0.0.1:8848/nacos ,默认用户名和密码都是 nacos

此时创建自己的配置

之后选择发布

2.引入依赖

java 复制代码
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>nacos-config-spring-boot-starter</artifactId>
    <version>0.2.12</version>
</dependency>


        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>

在application.yml中添加依赖

java 复制代码
# 配置中心
nacos:
  config:
    server-addr: 127.0.0.1:8848  # nacos 地址
    bootstrap:
      enable: true  # 预加载
    data-id: 这里填写上面你在配置文件里面填写的Data ID # 控制台填写的 Data ID
    group: DEFAULT_GROUP # 控制台填写的 group
    type: yaml  # 选择的文件格式
    auto-refresh: true # 开启自动刷新

2.使用

1.定义一个获取IP的方法

java 复制代码
package com.hhh.mianshiya.utils;

import java.net.InetAddress;
import javax.servlet.http.HttpServletRequest;

/**
 * 网络工具类
 *
 * @author <a href="https://github.com/liyupi">程序员鱼皮</a>
 * @from <a href="https://yupi.icu">编程导航知识星球</a>
 */
public class NetUtils {

    /**
     * 获取客户端 IP 地址
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                // 根据网卡取本机配置的 IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (inet != null) {
                    ip = inet.getHostAddress();
                }
            }
        }
        // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        if (ip == null) {
            return "127.0.0.1";
        }
        return ip;
    }

}

2.创建黑名单过滤工具类

java 复制代码
package com.hhh.mianshiya.blackfilter;

import cn.hutool.bloomfilter.BitMapBloomFilter;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.yaml.snakeyaml.Yaml;

import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
public class BlackIpUtils {

    // 声明一个静态的、线程安全的布隆过滤器实例
    private static volatile BitMapBloomFilter bloomFilter;
    
    /**
     * 判断 IP 是否在黑名单中
     * 
     * @param ip 待检查的 IP 地址
     * @return 如果 IP 地址在黑名单中,则返回 true;否则返回 false
     */
    public static boolean isBlackIp(String ip) {
        // 防御性编程,防止 bloomFilter 未初始化时调用
        if (bloomFilter == null) {
            log.warn("Bloom filter is not initialized. Returning false for IP: {}", ip);
            return false;
        }
        return bloomFilter.contains(ip);
    }
    
    /**
     * 重建黑名单布隆过滤器
     * 
     * @param configInfo 包含黑名单配置信息的字符串
     */
    public static void rebuildBlackIp(String configInfo) {
        // 处理空或无效的配置信息
        if (StrUtil.isBlank(configInfo)) {
            log.warn("你没有传递配置文件的内容");
            configInfo = "{}";
        }
    
        try {
            // 解析 Yaml 文件
            Yaml yaml = new Yaml();
            Map<String, Object> map = yaml.loadAs(configInfo, Map.class);
    
            // 获取黑名单列表
            List<String> blackIpList = (List<String>) map.getOrDefault("blackIpList", Collections.emptyList());
    
            synchronized (BlackIpUtils.class) {
                // 构建布隆过滤器
                BitMapBloomFilter bitMapBloomFilter = new BitMapBloomFilter(
                        Math.max(blackIpList.size(), 100)); // 设置合理的默认容量
    
                // 填充黑名单 IP
                for (String ip : blackIpList) {
                    bitMapBloomFilter.add(ip);
                }
    
                // 替换静态布隆过滤器
                bloomFilter = bitMapBloomFilter;
    
                log.info("Bloom filter rebuilt successfully with {} IPs.", blackIpList.size());
            }
        } catch (Exception e) {
            log.error("Failed to rebuild Bloom filter. Config info: {}", configInfo, e);
            // 如果发生异常,使用一个默认空的布隆过滤器
            synchronized (BlackIpUtils.class) {
                bloomFilter = new BitMapBloomFilter(100);
            }
        }
    }
}

3.创建Nacos配置监听类

在 blackfilter 包中新增监听器代码,追求性能的话可以自定义线程池

java 复制代码
package com.hhh.mianshiya.blackfilter;

import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.validation.constraints.NotNull;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

// 使用slf4j日志框架记录日志信息
@Slf4j
// 标识该类为Spring框架的组件,自动注入到Spring容器中
@Component
public class NacosListener implements InitializingBean {

    // 注入Nacos配置服务
    @NacosInjected
    private ConfigService configService;

    // 从配置中获取Nacos数据ID
    @Value("${nacos.config.data-id}")
    private String dataId;

    // 从配置中获取Nacos分组信息
    @Value("${nacos.config.group}")
    private String group;

    // 实现InitializingBean接口,当所有属性设置完毕后调用此方法
    @Override
    public void afterPropertiesSet() throws Exception {
        // 记录日志:nacos监听器启动
        log.info("nacos 监听器启动");

        // 从Nacos中获取配置信息,并添加配置变更监听器
        String config = configService.getConfigAndSignListener(dataId, group, 3000L, new Listener() {
            // 创建线程工厂,用于生成线程池中的线程
            final ThreadFactory threadFactory = new ThreadFactory() {
                // 用于生成线程池编号
                private final AtomicInteger poolNumber = new AtomicInteger(1);

                // 创建并配置线程
                @Override
                public 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);

            // 通过线程池异步处理黑名单变化的逻辑
            @Override
            public Executor getExecutor() {
                return executorService;
            }

            // 监听后续黑名单变化
            @Override
            public void receiveConfigInfo(String configInfo) {
                // 记录日志:监听到配置信息变化
                log.info("监听到配置信息变化:{}", configInfo);
                // 调用工具类方法,根据新的配置信息更新黑名单
                BlackIpUtils.rebuildBlackIp(configInfo);
            }
        });
        // 初始化黑名单
        BlackIpUtils.rebuildBlackIp(config);
    }
}

4.创建黑白名单过滤器

java 复制代码
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter")
public class BlackIpFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        String ipAddress = NetUtils.getIpAddress((HttpServletRequest) servletRequest);
        if (BlackIpUtils.isBlackIp(ipAddress)) {
            servletResponse.setContentType("text/json;charset=UTF-8");
            servletResponse.getWriter().write("{\"errorCode\":\"-1\",\"errorMsg\":\"黑名单IP,禁止访问\"}");
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

}

此时添加上这个过滤器之后需要在主类上面添加注解

复制代码
@ServletComponentScan

此时访问接口


总结

今天看了鱼皮的项目,第一次接触到了这种商业性质的思路,颇有感触,特写下博客记录

相关推荐
Theodore_102227 分钟前
7 设计模式原则之合成复用原则
java·开发语言·jvm·设计模式·java-ee·合成复用原则
我是苏苏33 分钟前
Web开发:ORM框架之使用Freesql的DbFrist封装常见功能
java·前端·jvm
武子康38 分钟前
Java-05 深入浅出 MyBatis - 配置深入 动态 SQL 参数、循环、片段
java·sql·设计模式·架构·mybatis·代理模式
拾荒的小海螺1 小时前
JAVA:探索 PDF 文字提取的技术指南
java·开发语言·pdf
村东头老张1 小时前
Java 实现PDF添加水印
java·开发语言·pdf
xiaoshiguang31 小时前
LeetCode 209 长度最小的子数组(滑动窗口)
java·算法·leetcode
xiaoshiguang31 小时前
Leetcode704. 二分查找
java·算法·leetcode
小屁孩大帅-杨一凡2 小时前
python获取本地电脑的ip和mac地址
java·服务器·网络·python·tcp/ip
sinat_384241092 小时前
修改Android Studio项目配置JDK路径和项目Gradle路径的GUI工具
android·java·android studio
琢瑜2 小时前
TCP 三次握手和四次挥手
网络·网络协议·tcp/ip·linux网络编程