赞
踩
IP黑白名单是网络安全管理中常见的策略工具,用于控制网络访问权限,根据业务场景的不同,其应用范围广泛,以下是一些典型业务场景:
服务器安全防护:
网站安全防护:
API接口保护:
比如比较容易被盗刷的短信接口、文件接口,都需要添加IP黑白名单加以限制。
@UtilityClass public class IpUtils { private final String UNKNOWN = "unknown"; private final String X_FORWARDED_FOR = "X-Forwarded-For"; private final String PROXY_CLIENT_IP = "Proxy-Client-IP"; private final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP"; private final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\s*,\s*"); /** * 默认情况下内网代理的子网可以是(后面有需要可以进行配置): * 1. 10/8 * 2. 192.168/16 * 3. 169.254/16 * 4. 127/8 * 5. 172.16/12 * 6. ::1 */ private 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 * * @return 请求的IP */ public String getIp() { var requestAttributes = RequestContextHolder.getRequestAttributes(); if (Objects.isNull(requestAttributes)) { return null; } var request = ((ServletRequestAttributes) requestAttributes).getRequest(); var ip = getRemoteIp(request); if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(PROXY_CLIENT_IP); } if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(WL_PROXY_CLIENT_IP); } if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } /** * 获取客户端真实IP地址,防止使用X-Forwarded-For进行IP伪造攻击,防御思路见类注释 * * @return 真实IP地址 */ private String getRemoteIp(HttpServletRequest request) { var remoteIp = request.getRemoteAddr(); var isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches(); if (isInternal) { var concatRemoteIpHeaderValue = new StringBuilder(); for (var e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); } concatRemoteIpHeaderValue.append(e.nextElement()); } var remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString()); for (var i = remoteIpHeaderValue.length - 1; i >= 0; i--) { var currentRemoteIp = remoteIpHeaderValue[i]; if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) { return currentRemoteIp; } } return null; } else { return remoteIp; } } private String[] commaDelimitedListToArray(String commaDelimitedStrings) { return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty()) ? new String[0] : COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings); } }
获取到客户端IP后,我们只要比对客户端IP是否在配置的白名单/黑名单中即可。
为了简化使用,可以采用注解的方式进行拦截。
@IpCheck
/** * IP白名单校验 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented @Inherited public @interface IpCheck { /** * 白名单IP列表,支持${...} */ @AliasFor("whiteList") String value() default ""; /** * 白名单IP列表,支持${...} */ @AliasFor("value") String whiteList() default ""; /** * 黑名单IP列表,支持${...} */ String blackList() default ""; }
IpCheckHandlerInterceptorImpl
我们实现HandlerInterceptor
,在接口上进行拦截,如果不满足配置的黑白名单,则抛出异常。
/** * @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a> * Created by gcdd1993 on 2023/9/20 */ @Component public class IpCheckHandlerInterceptorImpl implements HandlerInterceptor, EmbeddedValueResolverAware { private StringValueResolver stringValueResolver; @Override public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { // 检查是否有IpWhitelistCheck注解,并且是否开启IP白名单检查 if (!(handler instanceof HandlerMethod)) { return true; // 如果没有注解或者注解中关闭了IP白名单检查,则继续处理请求 } var handlerMethod = (HandlerMethod) handler; var method = handlerMethod.getMethod(); var annotation = AnnotationUtils.getAnnotation(method, IpCheck.class); if (annotation == null) { return true; } var clientIp = IpUtils.getIp(); // 检查客户端IP是否在白名单中 var whiteList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.whiteList())) .map(it -> it.split(",")) .orElse(new String[]{})) .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.toUnmodifiableSet()); if (!whiteList.isEmpty() && whiteList.contains(clientIp)) { return true; // IP在白名单中,继续处理请求 } var blackList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.blackList())) .map(it -> it.split(",")) .orElse(new String[]{})) .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.toUnmodifiableSet()); if (!blackList.isEmpty() && !blackList.contains(clientIp)) { return true; // IP不在黑名单中,继续处理请求 } // IP不在白名单中,可以返回错误响应或者抛出异常 // 例如,返回一个 HTTP 403 错误 throw new RuntimeException("Access denied, remote ip " + clientIp + " is not allowed."); } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.stringValueResolver = resolver; } }
核心逻辑写完了,该怎么使用呢?为了达到开箱即用的效果,我们可以接着新增自动装配的代码
IpCheckConfig
实现WebMvcConfigurer
接口,添加接口拦截器
/**
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
public class IpCheckConfig implements WebMvcConfigurer {
@Resource
private IpCheckHandlerInterceptorImpl ipCheckHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipCheckHandlerInterceptor);
}
}
@EnableIpCheck
参考@EnableScheduling
的实现,自己实现一个@EnableIpCheck
,该注解可以控制功能是否启用
/**
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Documented
@ComponentScan("xxx.ip") // 这里是IpCheckConfig的包名
@Import(IpCheckConfig.class)
public @interface EnableIpCheck {
}
简单地用代码来试验下效果
SampleApplication
@SpringBootApplication
@EnableIpCheck
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
@RestController @RequestMapping("/sample/ip-checker") public class IpCheckSample { @GetMapping("/white") @IpCheck(value = "0:0:0:0:0:0:0:1") String whiteList() { return "127.0.0.1"; } @GetMapping("/black") @IpCheck(blackList = "0:0:0:0:0:0:0:1") String blackList() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 */ @GetMapping("/all") @IpCheck(value = "0:0:0:0:0:0:0:1", blackList = "0:0:0:0:0:0:0:1") String all() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 * 支持解析Spring 配置文件 */ @GetMapping("/config") @IpCheck(value = "${digit.ip.check.white-list}", blackList = "${digit.ip.check.black-list}") String config() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 * 支持解析Spring 配置文件 */ @GetMapping("/black-config") @IpCheck(blackList = "${digit.ip.check.black-list}") String blackConfig() { return "127.0.0.1"; } }
由于本机请求IP地址是0:0:0:0:0:0:0:1
,所以这里使用0:0:0:0:0:0:0:1
而不是127.0.0.1
。
/sample/ip-checker/white
接口返回127.0.0.1
/sample/ip-checker/black
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
/sample/ip-checker/all
接口返回127.0.0.1
修改配置
digit:
ip:
check:
white-list: 127.0.0.1, 192.168.1.1, 192.168.1.2
black-list: 127.0.0.1, 192.168.1.1, 192.168.1.2,0:0:0:0:0:0:0:1
/sample/ip-checker/black-config
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
最后,可以结合配置中心,以便配置后立即生效。
IP黑白名单是网络安全管理中常见的策略工具,用于控制网络访问权限,根据业务场景的不同,其应用范围广泛,以下是一些典型业务场景:
服务器安全防护:
网站安全防护:
API接口保护:
比如比较容易被盗刷的短信接口、文件接口,都需要添加IP黑白名单加以限制。
@UtilityClass public class IpUtils { private final String UNKNOWN = "unknown"; private final String X_FORWARDED_FOR = "X-Forwarded-For"; private final String PROXY_CLIENT_IP = "Proxy-Client-IP"; private final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP"; private final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\s*,\s*"); /** * 默认情况下内网代理的子网可以是(后面有需要可以进行配置): * 1. 10/8 * 2. 192.168/16 * 3. 169.254/16 * 4. 127/8 * 5. 172.16/12 * 6. ::1 */ private 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 * * @return 请求的IP */ public String getIp() { var requestAttributes = RequestContextHolder.getRequestAttributes(); if (Objects.isNull(requestAttributes)) { return null; } var request = ((ServletRequestAttributes) requestAttributes).getRequest(); var ip = getRemoteIp(request); if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(PROXY_CLIENT_IP); } if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(WL_PROXY_CLIENT_IP); } if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } /** * 获取客户端真实IP地址,防止使用X-Forwarded-For进行IP伪造攻击,防御思路见类注释 * * @return 真实IP地址 */ private String getRemoteIp(HttpServletRequest request) { var remoteIp = request.getRemoteAddr(); var isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches(); if (isInternal) { var concatRemoteIpHeaderValue = new StringBuilder(); for (var e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); } concatRemoteIpHeaderValue.append(e.nextElement()); } var remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString()); for (var i = remoteIpHeaderValue.length - 1; i >= 0; i--) { var currentRemoteIp = remoteIpHeaderValue[i]; if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) { return currentRemoteIp; } } return null; } else { return remoteIp; } } private String[] commaDelimitedListToArray(String commaDelimitedStrings) { return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty()) ? new String[0] : COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings); } }
获取到客户端IP后,我们只要比对客户端IP是否在配置的白名单/黑名单中即可。
为了简化使用,可以采用注解的方式进行拦截。
@IpCheck
/** * IP白名单校验 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented @Inherited public @interface IpCheck { /** * 白名单IP列表,支持${...} */ @AliasFor("whiteList") String value() default ""; /** * 白名单IP列表,支持${...} */ @AliasFor("value") String whiteList() default ""; /** * 黑名单IP列表,支持${...} */ String blackList() default ""; }
IpCheckHandlerInterceptorImpl
我们实现HandlerInterceptor
,在接口上进行拦截,如果不满足配置的黑白名单,则抛出异常。
/** * @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a> * Created by gcdd1993 on 2023/9/20 */ @Component public class IpCheckHandlerInterceptorImpl implements HandlerInterceptor, EmbeddedValueResolverAware { private StringValueResolver stringValueResolver; @Override public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { // 检查是否有IpWhitelistCheck注解,并且是否开启IP白名单检查 if (!(handler instanceof HandlerMethod)) { return true; // 如果没有注解或者注解中关闭了IP白名单检查,则继续处理请求 } var handlerMethod = (HandlerMethod) handler; var method = handlerMethod.getMethod(); var annotation = AnnotationUtils.getAnnotation(method, IpCheck.class); if (annotation == null) { return true; } var clientIp = IpUtils.getIp(); // 检查客户端IP是否在白名单中 var whiteList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.whiteList())) .map(it -> it.split(",")) .orElse(new String[]{})) .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.toUnmodifiableSet()); if (!whiteList.isEmpty() && whiteList.contains(clientIp)) { return true; // IP在白名单中,继续处理请求 } var blackList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.blackList())) .map(it -> it.split(",")) .orElse(new String[]{})) .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.toUnmodifiableSet()); if (!blackList.isEmpty() && !blackList.contains(clientIp)) { return true; // IP不在黑名单中,继续处理请求 } // IP不在白名单中,可以返回错误响应或者抛出异常 // 例如,返回一个 HTTP 403 错误 throw new RuntimeException("Access denied, remote ip " + clientIp + " is not allowed."); } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.stringValueResolver = resolver; } }
核心逻辑写完了,该怎么使用呢?为了达到开箱即用的效果,我们可以接着新增自动装配的代码
IpCheckConfig
实现WebMvcConfigurer
接口,添加接口拦截器
/**
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
public class IpCheckConfig implements WebMvcConfigurer {
@Resource
private IpCheckHandlerInterceptorImpl ipCheckHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipCheckHandlerInterceptor);
}
}
@EnableIpCheck
参考@EnableScheduling
的实现,自己实现一个@EnableIpCheck
,该注解可以控制功能是否启用
/**
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Documented
@ComponentScan("xxx.ip") // 这里是IpCheckConfig的包名
@Import(IpCheckConfig.class)
public @interface EnableIpCheck {
}
简单地用代码来试验下效果
SampleApplication
@SpringBootApplication
@EnableIpCheck
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
@RestController @RequestMapping("/sample/ip-checker") public class IpCheckSample { @GetMapping("/white") @IpCheck(value = "0:0:0:0:0:0:0:1") String whiteList() { return "127.0.0.1"; } @GetMapping("/black") @IpCheck(blackList = "0:0:0:0:0:0:0:1") String blackList() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 */ @GetMapping("/all") @IpCheck(value = "0:0:0:0:0:0:0:1", blackList = "0:0:0:0:0:0:0:1") String all() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 * 支持解析Spring 配置文件 */ @GetMapping("/config") @IpCheck(value = "${digit.ip.check.white-list}", blackList = "${digit.ip.check.black-list}") String config() { return "127.0.0.1"; } /** * 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常 * 支持解析Spring 配置文件 */ @GetMapping("/black-config") @IpCheck(blackList = "${digit.ip.check.black-list}") String blackConfig() { return "127.0.0.1"; } }
由于本机请求IP地址是0:0:0:0:0:0:0:1
,所以这里使用0:0:0:0:0:0:0:1
而不是127.0.0.1
。
/sample/ip-checker/white
接口返回127.0.0.1
/sample/ip-checker/black
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
/sample/ip-checker/all
接口返回127.0.0.1
修改配置
digit:
ip:
check:
white-list: 127.0.0.1, 192.168.1.1, 192.168.1.2
black-list: 127.0.0.1, 192.168.1.1, 192.168.1.2,0:0:0:0:0:0:0:1
/sample/ip-checker/black-config
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
最后,可以结合配置中心,以便配置后立即生效。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。