一些恶意用户(可能是黑客、爬虫、DDoS 攻击者)可能频繁请求服务器资源,导致资源占用过高。因此我们需要一定的手段实时阻止可疑或恶意的用户,减少攻击风险。
本次练习使用到的是Nacos配合布隆过滤器实现动态IP黑白名单过滤
文章目录
目录
文章目录
一、IP黑白名单是什么?
二、使用步骤
1.使用Nacos
1.通过Nacos添加配置
2.引入依赖
2.使用
1.定义一个获取IP的方法
2.创建黑名单过滤工具类
3.创建Nacos配置监听类
4.创建黑白名单过滤器
总结
一、IP黑白名单是什么?
一些恶意用户(可能是黑客、爬虫、DDoS 攻击者)可能频繁请求服务器资源,导致资源占用过高。因此我们需要一定的手段实时阻止可疑或恶意的用户,减少攻击风险。
通过 IP 封禁,可以有效拉黑攻击者,防止资源被滥用,保障合法用户的正常访问。
对于我们的需求,不让拉进黑名单的 IP 访问任何接口。
二、使用步骤
1.使用Nacos
首先就是下载Nacos
通过网盘分享的文件:nacos
链接: https://pan.baidu.com/s/12-9UA6hUSlEeyuKVfNPkJw?pwd=fr2z 提取码: fr2z
--来自百度网盘超级会员v6的分享
拿到文件夹内容是
打开bin目录
//运行命令行
startup.sh -m standalone
看到这个界面就代表着Nacos运行成功了
1.通过Nacos添加配置
访问:http://127.0.0.1:8848/nacos ,默认用户名和密码都是 nacos
此时创建自己的配置
之后选择发布
2.引入依赖
<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中添加依赖
# 配置中心
nacos:config:server-addr: 127.0.0.1:8848 # nacos 地址bootstrap:enable: true # 预加载data-id: 这里填写上面你在配置文件里面填写的Data ID # 控制台填写的 Data IDgroup: DEFAULT_GROUP # 控制台填写的 grouptype: yaml # 选择的文件格式auto-refresh: true # 开启自动刷新
2.使用
1.定义一个获取IP的方法
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")) {// 根据网卡取本机配置的 IPInetAddress 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.创建黑名单过滤工具类
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)); // 设置合理的默认容量// 填充黑名单 IPfor (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 包中新增监听器代码,追求性能的话可以自定义线程池
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配置服务@NacosInjectedprivate ConfigService configService;// 从配置中获取Nacos数据ID@Value("${nacos.config.data-id}")private String dataId;// 从配置中获取Nacos分组信息@Value("${nacos.config.group}")private String group;// 实现InitializingBean接口,当所有属性设置完毕后调用此方法@Overridepublic 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);// 创建并配置线程@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;}// 监听后续黑名单变化@Overridepublic void receiveConfigInfo(String configInfo) {// 记录日志:监听到配置信息变化log.info("监听到配置信息变化:{}", configInfo);// 调用工具类方法,根据新的配置信息更新黑名单BlackIpUtils.rebuildBlackIp(configInfo);}});// 初始化黑名单BlackIpUtils.rebuildBlackIp(config);}
}
4.创建黑白名单过滤器
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter")
public class BlackIpFilter implements Filter {@Overridepublic 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
此时访问接口
总结
今天看了鱼皮的项目,第一次接触到了这种商业性质的思路,颇有感触,特写下博客记录