文章目录
- Code
- IpAccessInterceptor
- addInterceptor
- 工具类
- 配置文件 application.yml
- 单元测试
Code
废话不多说,直接上码
IpAccessInterceptor
package cn.cloud.bus.module.servicebus.framework.ipconfig;import cn.cloud.bus.module.servicebus.util.IpFilterUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;
import java.util.stream.Collectors;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* https://juejin.cn/post/7369582512236167194*/@Slf4j
@Component
public class IpAccessInterceptor implements HandlerInterceptor {@Value("${bus.ip.blackIpList}")private Set<String> blackIpList;@Value("${bus.ip.whiteIpList}")private Set<String> whiteIpList;@Value("${bus.ip.innerEnable}")private boolean innerEnable;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String ip = IpFilterUtil.getIpAddress();// ip 为空,直接拒绝访问if (!StringUtils.hasLength(ip)) {log.error("IpAccessInterceptor got IpAddress is null or empty" );return false;}// 开启内网IP匹配,内网地址直接放过if (innerEnable && IpFilterUtil.INTERNAL_PROXIES.matcher(ip).matches()) {return true;}// 在黑名单中直接拒绝访问if (!CollectionUtils.isEmpty(blackIpList) && IpFilterUtil.containIp(ip, blackIpList.stream().collect(Collectors.joining(";")))) {log.warn("ip:{} 在黑名单中拒绝访问.....", ip);return false;}if (!CollectionUtils.isEmpty(whiteIpList) && !IpFilterUtil.containIp(ip, whiteIpList.stream().collect(Collectors.joining(";")))) {// 不在白名单中,也拒绝访问log.warn("ip:{} 不在白名单中拒绝访问.....", ip);return false;}// 验证通过return true;}
}
addInterceptor
package cn.cloud.bus.module.servicebus.framework.ipconfig;import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* <p>* @Order 注解的值越小,优先级越高。* <p>* 需要注意的是,@Order 注解只影响WebMvcConfigurer的执行顺序,不影响其他Spring组件的顺序。* 如果有多个WebMvcConfigurer配置需要被依次执行,确保每个配置类上都指定了@Order注解,并为它们分配适当的顺序值*/
@Configuration
@Order(1)
public class IpConfig implements WebMvcConfigurer {@Resourceprivate IpAccessInterceptor ipAccessInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 添加拦截器,支持多个路径模式registry.addInterceptor(ipAccessInterceptor).addPathPatterns("/**");}
}
工具类
package cn.cloud.bus.module.servicebus.util;import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;/*** The IP Filter* <p>** @author artisan*/
public class IpFilterUtil {private static final String UNKNOWN = "unknown";private static final String X_FORWARDED_FOR = "X-Forwarded-For";private static final String PROXY_CLIENT_IP = "Proxy-Client-IP";private static final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";private static final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\\s*,\\s*");/*** IP格式的正则*/private static final Pattern PATTERN = Pattern.compile("(1\\d{1,2}|2[0-4]\\d|25[0-5]|\\d{1,2})\\."+ "(1\\d{1,2}|2[0-4]\\d|25[0-5]|\\d{1,2})\\."+ "(1\\d{1,2}|2[0-4]\\d|25[0-5]|\\d{1,2})\\."+ "(1\\d{1,2}|2[0-4]\\d|25[0-5]|\\d{1,2})");/*** 默认情况下内网代理的子网可以是(主要用于检测请求是否来自内网代理或本地机器):* 1. 10/8 任何以 10. 开头的 IPv4 地址。这通常用于本地网络* 2. 192.168/16 任何以 192.168. 开头的 IPv4 地址。这也是常见的本地网络地址* 3. 169.254/16 任何以 169.254. 开头的 IPv4 地址。这些地址用于链接本地地址(Link-local addresses)* 4. 127/8 任何以 127. 开头的 IPv4 地址。这些是回环地址,用于指向本地计算机* 5. 172.16/12 包括 172.16.0.0 到 172.31.255.255 的 IPv4 地址范围。这些地址用于专用网络* 6. ::1 用于 IPv6 中的回环地址,等同于 IPv4 的 127.0.0.1*/public static final Pattern INTERNAL_PROXIES = Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +"192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +"169\\.254\\.\\d{1,3}\\.\\d{1,3}|" +"127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +"172\\.1[6-9]\\.\\d{1,3}\\.\\d{1,3}|" +"172\\.2[0-9]\\.\\d{1,3}\\.\\d{1,3}|" +"172\\.3[0-1]\\.\\d{1,3}\\.\\d{1,3}|" +"0:0:0:0:0:0:0:1|::1");/*** 获取当前请求的客户端IP地址。* 优先从HTTP_X_FORWARDED_FOR获取IP地址,因为这个字段在通过代理转发请求时会被设置。* 如果HTTP_X_FORWARDED_FOR为空或者未被设置,那么会尝试从HTTP_CLIENT_IP字段获取IP地址。* 如果这两个字段都为空或者未被设置,那么会退回到请求的REMOTE_ADDR属性,这个属性代表了与服务器直接连接的客户端IP。* 注意,这个方法只能获取到经过最后一次代理转发的IP地址,如果请求经过了多次代理,且代理服务器没有正确设置HTTP_X_FORWARDED_FOR字段,那么可能获取到的是代理服务器的IP地址。** @return 当前请求的客户端IP地址,如果无法获取则返回null。*/public static String getIpAddress() {// 获取当前请求的属性,这通常是一个ServletRequestAttributes对象。RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 如果请求属性为空,则无法获取IP地址,直接返回null。if (Objects.isNull(requestAttributes)) {return null;}// 从请求属性中获取HttpServletRequest对象,这个对象包含了HTTP请求的各种信息。HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();// 尝试从HTTP_X_FORWARDED_FOR字段获取客户端IP地址。String ip = getRemoteIp(request);// 如果获取的IP为空或者等于"unknown",则尝试从HTTP_CLIENT_IP字段获取IP地址。if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(PROXY_CLIENT_IP);}// 如果获取的IP仍然为空或者等于"unknown",则尝试从WL_PROXY_CLIENT_IP字段获取IP地址。if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(WL_PROXY_CLIENT_IP);}// 如果所有尝试都未能获取到有效的IP地址,则退回到请求的REMOTE_ADDR属性。if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}/*** 获取客户端真实IP地址。* 本方法旨在处理通过代理的情况,通过检查X-Forwarded-For头部来获取真实IP。* 防止使用X-Forwarded-For进行IP伪造攻击,只信任内部代理转发的IP信息。** @param request HttpServletRequest对象,用于获取客户端请求信息。* @return 客户端真实IP地址,如果无法确定则返回null。*/private static String getRemoteIp(HttpServletRequest request) {// 获取请求的客户端IP地址String remoteIp = request.getRemoteAddr();// 判断是否为内部代理IPboolean isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches();if (isInternal) {// 拼接X-Forwarded-For头部的所有IP地址StringBuilder concatRemoteIpHeaderValue = new StringBuilder();for (Enumeration<String> e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) {if (concatRemoteIpHeaderValue.length() > 0) {concatRemoteIpHeaderValue.append(", ");}concatRemoteIpHeaderValue.append(e.nextElement());}// 将拼接的IP地址字符串转换为数组String[] remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString());// 从数组末尾向前遍历,寻找第一个非内部代理IPfor (int i = remoteIpHeaderValue.length - 1; i >= 0; i--) {String currentRemoteIp = remoteIpHeaderValue[i];if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) {// 找到第一个非内部代理IP,返回之return currentRemoteIp;}}// 如果所有IP都是内部代理IP,则返回nullreturn null;} else {// 如果不是通过内部代理的请求,直接返回获取的IP地址return remoteIp;}}/*** 将以逗号分隔的字符串转换为字符串数组。* <p>* 此方法用于处理以逗号分隔的字符串,将它们分割成单独的字符串元素,并返回一个包含这些元素的数组。* 如果输入的字符串为null或为空,则返回一个空数组,而不是null,以避免调用方处理null值。** @param commaDelimitedStrings 一个以逗号分隔的字符串,可能包含或不包含空格。* @return 一个字符串数组,包含输入字符串中所有的逗号分隔的元素。*/private static String[] commaDelimitedListToArray(String commaDelimitedStrings) {// 如果输入字符串为null或为空,则返回空数组return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty())? new String[0]: COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings);}/*** 根据IP地址,及IP白名单设置规则判断IP是否包含在白名单 <br>* <br>* 使用场景: 是否包含在白名单或黑名单中.** @param ip ip address* @param ipRuleConfig ip rule config* @return contain return true*/public static boolean containIp(String ip, String ipRuleConfig) {return checkContainIp(ip, getAllowIpList(ipRuleConfig));}/*** 根据指定的允许IP地址字符串,解析并返回一个包含所有有效IP地址的集合。* 允许IP地址字符串格式可以包含通配符"*",表示该段可以是任意数值。* 例如:"192.168.*.10"表示192.168.0.10到192.168.255.10之间的所有IP地址。** @param allowIp 允许的IP地址字符串,各IP地址间用分号";"分隔。* @return 包含所有有效允许IP地址的集合。*/public static Set<String> getAllowIpList(String allowIp) {// 初始化一个无序、不重复的IP地址集合Set<String> ipList = new HashSet<>();// 如果允许IP地址字符串为空,则直接返回空集合if (!StringUtils.hasLength(allowIp)){return ipList;}// 分割并遍历允许的IP地址字符串,去除所有空格for (String allow : allowIp.replaceAll("\\s", "").split(";")) {// 如果当前IP地址包含通配符"*"if (allow.contains("*")) {// 根据"."分割IP地址段String[] ips = allow.split("\\.");// 初始化起始和结束IP地址的每段默认值String[] from = new String[]{"0", "0", "0", "0"};String[] end = new String[]{"255", "255", "255", "255"};// 用于存储完成通配符扩展的IP地址段List<String> tem = new ArrayList<>();// 遍历每段IP地址,处理通配符for (int i = 0; i < ips.length; i++) {if (ips[i].contains("*")) {// 对包含通配符的段进行扩展tem = complete(ips[i]);// 将起始和结束IP的对应段置为null,表示该段有通配符from[i] = null;end[i] = null;} else {// 如果不包含通配符,直接设置起始和结束IP的对应段from[i] = ips[i];end[i] = ips[i];}}// 构建起始IP地址字符串StringBuilder fromIP = new StringBuilder();// 构建结束IP地址字符串StringBuilder endIP = new StringBuilder();// 遍历每段IP地址,生成起始和结束IP地址字符串for (int i = 0; i < 4; i++) {if (from[i] != null) {fromIP.append(from[i]).append(".");endIP.append(end[i]).append(".");} else {// 如果该段有通配符,用"[*]"表示fromIP.append("[*].");endIP.append("[*].");}}// 删除最后一个"."fromIP.deleteCharAt(fromIP.length() - 1);endIP.deleteCharAt(endIP.length() - 1);// 遍历通配符扩展的结果,生成并验证每个可能的IP地址for (String s : tem) {// 生成完整的IP地址范围字符串String ip = fromIP.toString().replace("[*]", s.split(";")[0]) + "-" + endIP.toString().replace("[*]", s.split(";")[1]);// 如果IP地址范围有效,添加到结果集合中if (validate(ip)) {ipList.add(ip);}}} else {// 如果当前IP地址不包含通配符,直接验证并添加到结果集合中if (validate(allow)) {ipList.add(allow);}}}// 返回包含所有有效IP地址的集合return ipList;}/*** 检查给定的IP地址是否在指定的IP地址段集合中。* <p>* 此方法支持直接IP地址的匹配,以及IP地址段的匹配。IP地址段通过"-"符号分隔,比如"192.168.1.1-192.168.1.255"。** @param ip 待检查的IP地址。* @param ipList IP地址段集合,包含直接的IP地址和IP地址段。* @return 如果给定的IP地址在集合中或在任何地址段内,则返回true;否则返回false。*//*** 根据IP,及可用Ip列表来判断ip是否包含在白名单之中** @param ip ip address* @param ipList ip address list* @return contain return true*/private static boolean checkContainIp(String ip, Set<String> ipList) {// 如果IP地址列表为空或直接包含待检查的IP,则无需进一步检查,直接返回true。if (ipList.isEmpty() || ipList.contains(ip)) {return true;} else {// 遍历IP地址段列表。for (String allow : ipList) {// 检查当前条目是否为IP地址段。if (allow.contains("-")) {// 分割IP地址段的起始和结束IP。String[] from = allow.split("-")[0].split("\\.");String[] end = allow.split("-")[1].split("\\.");String[] tag = ip.split("\\.");// 逐段比较给定IP是否在地址段范围内。// 对IP从左到右进行逐段匹配boolean check = true;for (int i = 0; i < 4; i++) {int s = Integer.valueOf(from[i]);int t = Integer.valueOf(tag[i]);int e = Integer.valueOf(end[i]);// 如果任何一段不在范围内,则跳出循环。if (!(s <= t && t <= e)) {check = false;break;}}// 如果所有段都在范围内,则返回true。if (check) {return true;}}}}// 如果没有匹配到任何IP地址或地址段,则返回false。return false;}/*** 根据给定的IP地址部分,完成该部分的范围限定。* 该方法主要用于处理IP地址的一部分,通过补全最小和最大值,来定义一个范围。* 例如,对于单个数字“1”,补全后得到“0;255”,表示0到255的范围。* 对于两个数字“10”,补全后可以得到“10;19”和“100;199”,分别表示10到19和100到199的范围。** @param arg 表示IP地址的一部分,可以是1到3个数字。* @return 返回一个包含补全后范围的字符串列表。如果无法补全,则返回空。*/private static List<String> complete(String arg) {List<String> result = new ArrayList<>();// 当arg只有一个数字时,补全为0到255的范围if (arg.length() == 1) {result.add("0;255");} else if (arg.length() == 2) {// 尝试将arg的第一个数字视为完整部分,补全第二个数字的范围String s1 = complete(arg, 1);if (s1 != null) {result.add(s1);}// 尝试将arg的第二个数字视为完整部分,补全第一个数字的范围String s2 = complete(arg, 2);if (s2 != null) {result.add(s2);}} else {// 对于三个数字的情况,只补全最后一个数字的范围String s1 = complete(arg, 1);if (s1 != null) {result.add(s1);}}return result;}/*** 根据给定的参数完成IP地址段的生成。* 参数arg是一个简化表示的IP地址,其中"*"代表一个未知的数字位。* 函数的目的是根据arg的长度和内容,推断出这个IP地址段可能的最小值和最大值。** @param arg 简化表示的IP地址,"*"代表一个未知的数字位。* @param length arg中"*"的长度,用于确定未知数字位的位数。* @return 返回一个字符串,包含最小和最大IP地址的表示,用分号分隔。如果最小值超过255,则返回null。*/private static String complete(String arg, int length) {// 根据length的值,确定最小值和最大值的替代字符串String from, end;if (length == 1) {from = arg.replace("*", "0");end = arg.replace("*", "9");} else {from = arg.replace("*", "00");end = arg.replace("*", "99");}// 检查最小值是否超过255,如果超过,则无法生成有效的IP地址段,返回nullif (Integer.valueOf(from) > 255) {return null;}// 检查最大值是否超过255,如果超过,则将最大值限定为255if (Integer.valueOf(end) > 255) {end = "255";}// 返回最小值和最大值的字符串表示,用分号分隔return from + ";" + end;}/*** 验证IP地址字符串是否符合指定格式。* <p>* 该方法用于校验传入的IP地址字符串是否符合特定的格式要求,具体格式要求未在注释中明确,假设是由PATTERN变量定义的。* 方法通过将IP地址字符串按连字符“-”分割,然后逐个检查分割后的部分是否符合格式要求。* 如果所有部分都符合要求,则返回true;如果任何一部分不符合要求,则返回false。** @param ip 待验证的IP地址字符串,可能包含一个或多个IP地址段,段之间用连字符“-”分隔。* @return 如果IP地址字符串的所有部分都符合格式要求,则返回true;否则返回false。*/private static boolean validate(String ip) {// 将IP地址字符串按连字符“-”分割成多个部分for (String s : ip.split("-")) {// 使用预定义的PATTERN对每个部分进行格式校验if (!PATTERN.matcher(s).matches()) {// 如果任何一部分不符合格式要求,则立即返回falsereturn false;}}// 如果所有部分都通过了格式校验,则返回truereturn true;}}
配置文件 application.yml
单元测试
import cn.cloud.bus.module.servicebus.util.IpFilterUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@RunWith(MockitoJUnitRunner.class)
@Slf4j
public class IpAccessInterceptorTest {private Set<String> blackIpList;private Set<String> whiteIpList;private List<String> remoteIpList;@Beforepublic void setUp() {blackIpList = new HashSet<>();whiteIpList = new HashSet<>();remoteIpList = new ArrayList<>();blackIpList.add("192.168.1.1");blackIpList.add("192.168.2.*");blackIpList.add("192.168.3.17-192.168.3.38");whiteIpList.add("192.168.1.1");whiteIpList.add("192.168.2.*");whiteIpList.add("192.168.3.17-192.168.3.38");remoteIpList.add("192.168.1.1");remoteIpList.add("192.168.2.2");remoteIpList.add("192.168.3.16");remoteIpList.add("192.168.3.18");remoteIpList.add("10.11.110.151");}@Testpublic void preHandle_IpCheck() throws Exception {log.info("黑名单列表:{}", blackIpList.toString());blackTest();log.info("白名单列表:{}", whiteIpList.toString());whiteTest();log.info("================no config value======================");blackIpList.clear();whiteIpList.clear();log.info("黑名单列表:{}", blackIpList.toString());blackTest();log.info("白名单列表:{}", whiteIpList.toString());whiteTest();}private void whiteTest() {for (String ip : remoteIpList) {if (!CollectionUtils.isEmpty(whiteIpList) && !IpFilterUtil.containIp(ip, whiteIpList.stream().collect(Collectors.joining(";")))) {// 不在白名单中,也拒绝访问log.warn("ip:{} 不在白名单中拒绝访问.....", ip);} else {log.info("ip:{} 在白名单中允许访问.....", ip);}}}private void blackTest() {for (String ip : remoteIpList) {if (!CollectionUtils.isEmpty(blackIpList) && IpFilterUtil.containIp(ip, blackIpList.stream().collect(Collectors.joining(";")))) {log.warn("ip:{} 在黑名单中拒绝访问.....", ip);} else {log.info("ip:{} 不在黑名单中允许访问.....", ip);}}}}
package cn.cloud.bus.module.servicebus.ip;import cn.cloud.bus.module.servicebus.util.IpFilterUtil;import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;import static org.junit.Assert.*;
import java.util.HashSet;
import java.util.Set;import static org.junit.jupiter.api.Assertions.assertEquals;public class IpFilterTest {@InjectMocksprivate IpFilterUtil ipFilterUtil;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);}@Testpublic void test() {// 暂不支持 192.168.1.0/24 CIDR表示法String ipWhilte = "192.168.1.1;" + //设置单个IP的白名单"192.168.2.*;" + //设置ip通配符,对一个ip段进行匹配"192.168.3.17-192.168.3.38"; //设置一个IP范围boolean flag = IpFilterUtil.containIp("192.168.2.2", ipWhilte);boolean flag2 = IpFilterUtil.containIp("192.168.1.2", ipWhilte);boolean flag3 = IpFilterUtil.containIp("192.168.3.16", ipWhilte);boolean flag4 = IpFilterUtil.containIp("192.168.3.18", ipWhilte);System.out.println(flag); //trueSystem.out.println(flag2); //falseSystem.out.println(flag3); //falseSystem.out.println(flag4); //true}@Testpublic void testGetAllowIpList_ValidIpList_ReturnsCorrectSet() {String allowIp = "192.168.1.1;192.168.*.10;10.0.0.1;10.*.*.1";Set<String> expectedIpList = new HashSet<>();expectedIpList.add("192.168.1.1");expectedIpList.add("192.168.0.10-192.168.255.10");expectedIpList.add("10.0.0.1");expectedIpList.add("10.0.0.1-10.255.255.1");Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_InvalidIpList_ReturnsEmptySet() {String allowIp = "invalid_ip;192.168.1.1";Set<String> expectedIpList = new HashSet<>();expectedIpList.add("192.168.1.1");Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_EmptyString_ReturnsEmptySet() {String allowIp = "";Set<String> expectedIpList = new HashSet<>();Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_NullString_ReturnsEmptySet() {String allowIp = null;Set<String> expectedIpList = new HashSet<>();Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_OnlyWhitespace_ReturnsEmptySet() {String allowIp = " ";Set<String> expectedIpList = new HashSet<>();Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_InvalidRangeIpList_ReturnsCorrectSet() {String allowIp = "192.168.1.1-192.168.2.2";Set<String> expectedIpList = new HashSet<>();expectedIpList.add("192.168.1.1-192.168.2.2");Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}@Testpublic void testGetAllowIpList_MixedValidAndInvalidIpList_ReturnsCorrectSet() {String allowIp = "192.168.1.1;invalid_ip;192.168.*.10";Set<String> expectedIpList = new HashSet<>();expectedIpList.add("192.168.1.1");expectedIpList.add("192.168.0.10-192.168.255.10");Set<String> actualIpList = IpFilterUtil.getAllowIpList(allowIp);Assert.assertEquals(expectedIpList, actualIpList);}private String ip;private String ipRuleConfig;@Testpublic void containIp_IpInWhiteList_ShouldReturnTrue() {ip = "192.168.1.1";ipRuleConfig = "192.168.1.1;192.168.1.2";assertTrue(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_IpNotInWhiteList_ShouldReturnFalse() {ip = "192.168.1.3";ipRuleConfig = "192.168.1.1;192.168.1.2";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_EmptyIpRuleConfig_ShouldReturnFalse() {ip = "192.168.1.1";ipRuleConfig = "";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_NullIpRuleConfig_ShouldReturnFalse() {ip = "192.168.1.1";ipRuleConfig = null;assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_InvalidIp_ShouldReturnFalse() {ip = "invalid_ip";ipRuleConfig = "192.168.1.1;192.168.1.2";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_IpWithPort_ShouldReturnTrue() {ip = "192.168.1.1:8080";ipRuleConfig = "192.168.1.1";assertTrue(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_IpWithPort_ShouldReturnFalse() {ip = "192.168.1.1:8080";ipRuleConfig = "192.168.1.2";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_NullIp_ShouldReturnFalse() {ip = null;ipRuleConfig = "192.168.1.1;192.168.1.2";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}@Testpublic void containIp_EmptyIp_ShouldReturnFalse() {ip = "";ipRuleConfig = "192.168.1.1;192.168.1.2";assertFalse(IpFilterUtil.containIp(ip, ipRuleConfig));}
}