Spring Boot - 优雅实现支持通配符和IP段的IP访问黑白名单机制

文章目录

  • 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));}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/50741.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解计算机系统 CSAPP 家庭作业11.10

A: //home.html <form action"/cgi-bin/adder" method"GET"><ul><li><label for"n1">n1:</label><input type"text" id"n1" name"n1" /> //name的值决定页面提交后&#xf…

栈知识梳理和函数实现

参考此文章数据结构——栈&#xff0c;此文章写的更详细&#xff0c;由于我们都是学自于比特课程&#xff0c;这里做个自我备份&#xff0c;方便后续查阅、修改和补充。 栈知识梳理和函数实现 前言1.栈是什么&#xff1f;2.栈的接口实现2.1初始化栈2.2入栈2.3 出栈2.4 获取栈顶…

C语言图书信息管理系统

题目&#xff1a;图书信息管理系统 内容及主要功能描述&#xff1a; 该系统用于管理图书信息&#xff0c;包括图书的增加、删除、查找、修改、浏览、按出版社统计图书数量等功能。具体功能包括&#xff1a; 增加图书&#xff1a;输入图书信息并添加到系统中。删除图书&#x…

【漏洞复现】phpStudy 小皮 Windows面板 存在RCE漏洞

靶场资料后台自行领取【靶场】 image-20240726092307252 PhpStudy小皮面板曝RCE漏洞&#xff0c;本质是存储型XSS引发。攻击者通过登录用户名输入XSS代码&#xff0c;结合后台计划任务功能&#xff0c;实现远程代码执行&#xff0c;严重威胁服务器安全。建议立即更新至安全版…

JAVA SE 类和对象

类和对象 类定义和使用类的定义格式 类的实例化什么是实例化 this 引用this引用的特性 对象的构造及初始化如何初始化对象构造方法概念特性 在这里插入图片描述 **注意**&#xff1a; 封装封装的概念封装扩展之包导入包中的类自定义包包的访问权限控制举例 static成员static修饰…

【计算机网络】TCP协议详解

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 1、引言2、udp和tcp协议的异同3、tcp服务器3.1、接口认识3.2、服务器设计 4、tcp客户端4.1、客户端设计4.2、说明 5、再研Tcp服务端5.1、多进程版5.2、多线程版 5、守护进程化5.1、什么是守护进程5.2…

古籍双层PDF制作教程:保姆级古籍数字化教程

在智慧古籍数字化项目中&#xff0c;很多图书馆要求将古籍导出为双层PDF&#xff0c;并且确保输出双层PDF底层文本与上层图片偏移量控制在1毫米以内。那么本教程带你使用古籍数字化平台&#xff0c;3分钟把一个古籍书籍转化为双侧PDF。 第1步&#xff1a;上传古籍 点批量上传…

云服务器Ubuntu18.04进行Nginx配置

云服务器镜像版本信息&#xff1a;Ubuntu 18.04 server 64bit&#xff0c;本文记录了在改版本镜像上安装Nginx&#xff0c;并介绍了Nginx配置文件目录&#xff0c;便于后面再次有需求时进行复习。 文章目录 Nginx的安装Nginx配置文件分析 Nginx的安装 1.执行下面命令进行安装…

玩转CSS:用ul li +JS 模拟select,避坑浏览器不兼容。

玩转CSS&#xff1a;用ul li JS 模拟select&#xff0c;避坑浏览器不兼容。 在前端的工作中&#xff0c;经常会遇到 selcet控件&#xff0c;但我们用css来写它的样式时候&#xff0c;总是不那么令人满意&#xff0c;各种浏览器不兼容啊有没有&#xff1f; 那么&#xff0c;我…

西电网络空间安全综合953考研分享||西安电子科技大学

一、院校选择 如何选择适合自己的学校以及专业 1. 首先要对自己选择的学校有热情&#xff0c;选择自己最想去的学校 2. 其次选择在自己能力范围内努力能考上的学校&#xff0c;综合考虑地区&#xff08;不同地区公共课分数有一定的差别&#xff09;、学校&#xff08;建议跨…

Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后&#xff0c;我们把精力放到节点勾选功能实现上来。**注意&#xff0c;对于组件的开发者来说&#xff0c;要充分考虑用户的使用场景&#xff0c;组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说&#xff0c;用户…

如何保证前后端交互信息不被篡改。

先说说前后端有哪些认证方式来保证&#xff1a; 基于 session 的认证方式&#xff1a;前端在用户登录成功后&#xff0c;后端会在服务器端生成一个唯一的 session ID&#xff0c;并将该 session ID 返回给前端&#xff0c;在后续的请求中&#xff0c;前端需要带上该 session ID…

【CUDA Runtime】第一个“Hello World“程序

文章目录 前言前提须知CUDA Runtime 简介核心功能优势和应用 使用CudaRuntime进行第一个"Hello world"程序创建CudaRuntime工程选择GPU函数原型参数返回值作用 获取支持Cuda的GPU信息获取支持Cuda的GPU数量获取设备属性运行展示 在GPU上分配内存把需要运行的主机内存…

数据库密码实现加盐加密处理

在实际的开发中&#xff0c;我们的数据库密码一般都是明文的方式存储在数据库中&#xff0c;但是&#xff0c;这种操作非常不安全&#xff0c;容易被黑&#xff01; 那么&#xff0c;此时我们就需要对其进行加密处理&#xff0c;市面上比较常见的就是MD5加密了&#xff0c;但是…

【Linux】syscall sys_write流程摸索

这是通过tty进行摸索sys_write的流程。 在前面的博客里&#xff0c;我们可以看到基于内核C语言源代码日志打印&#xff0c;在打印的日志里边包含&#xff1a;日期&#xff0c;时间&#xff0c;当前文件所在代码目录&#xff0c;当前执行函数名&#xff0c;当前文件执行行号&am…

运维团队如何借助分布式部署提升监控效率与可靠性

随着企业IT基础设施的日益复杂和分布式架构的广泛应用&#xff0c;传统的监控解决方案已经难以满足现代运维团队的需求。在这样的背景下&#xff0c;分布式部署作为一种新型的监控架构&#xff0c;以其灵活性、可扩展性和高可用性&#xff0c;成为了运维团队提升监控效率与可靠…

C++模版基础知识与STL基本介绍

目录 一. 泛型编程 二. 函数模板 1. 概念 2. 函数模版格式 3. 函数模版的原理 4. 模版函数的实例化 (1). 隐式实例化 (2.) 显式实例化 5. 模版参数的匹配原则 三. 类模板 1. 类模板的定义格式 2. 类模板的实例化 四. STL的介绍 1. 什么是STL&#xff1f; 2. STL的版…

3.5-RNN文本生成

1语言模型生成文本的顺序 前面我们已经能够实现使用下图的LSTM网络进行语言建模&#xff1b; 对于一个已经在语料库上学习好的LSTM模型&#xff1b;如果语料库就只是you say goobye and i say hello&#xff1b;那么当把单词i输入到模型中&#xff0c;Time xxx层的第一个LSTM…

苍穹外卖01

0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 &#xff08;nginx.exe要在非中文的目录下&#xff09; 开启服务&#xff1a; start nginx 查看任务进程是否存在&#xff1a; tasklist /fi "imagename eq nginx.exe" 关闭ngi…

中文之美,美在辞藻富丽,也美在情感含蓄内敛。

文章目录 引言句句不提幸福,句句都是幸福句句不提释怀,句句都是释怀句句不提爱意,句句都是爱意句句不提安慰,句句都是安慰句句不提遗憾,句句都是遗憾句句不提思念,句句都是思念引言 许多句子没有将主题直抒胸臆,却通过字词间的呼应、碰撞,让人感受到“言未表而意无穷”…