一些恶意用户(可能是黑客、爬虫、DDoS 攻击者)可能频繁请求服务器资源,导致资源占用过高。因此我们需要一定的手段实时阻止可疑或恶意的用户,减少攻击风险。
本次练习使用到的是Nacos配合布隆过滤器实现动态IP黑白名单过滤
文章目录
目录
一、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
此时访问接口
总结
今天看了鱼皮的项目,第一次接触到了这种商业性质的思路,颇有感触,特写下博客记录