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

此时访问接口


总结

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

相关推荐
范什么特西几秒前
web练习
java·前端·javascript
阿捞28 分钟前
JVM排查工具单
java·jvm·python
mfxcyh17 分钟前
基于xml、注解、JavaConfig实现spring的ioc
xml·java·spring
Flittly18 分钟前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
xdscode26 分钟前
Spring 依赖注入方式全景解析
java·后端·spring
情绪雪27 分钟前
IP 协议基本原理
网络·网络协议·tcp/ip
爱吃烤鸡翅的酸菜鱼36 分钟前
Java 事件发布-订阅机制全解析:从原生实现到主流中间件
java·中间件·wpf·事件·发布订阅
无限码力1 小时前
华为OD技术面真题 - JAVA开发- spring框架 - 7
java·开发语言·华为od·华为od面试真题·华为odjava八股文·华为odjava开发题目·华为odjava开发高频题目
Lyyaoo.1 小时前
【JAVA基础面经】JAVA中的异常
java·开发语言
my_styles1 小时前
linux系统下安装 tengine / 宝兰德等国产信创中间件和闭坑
linux·运维·服务器·spring boot·nginx·中间件