基于SpringBoot IP黑白名单的实现2

业务场景

IP黑白名单是网络安全管理中常见的策略工具,用于控制网络访问权限,根据业务场景的不同,其应用范围广泛,以下是一些典型业务场景:

  1. 服务器安全防护

    • 黑名单:可以用来阻止已知的恶意IP地址或曾经尝试攻击系统的IP地址,防止这些来源对服务器进行未经授权的访问、扫描、攻击等行为。
    • 白名单:仅允许特定IP或IP段访问关键服务,比如数据库服务器、内部管理系统等,实现最小授权原则,降低被未知风险源入侵的可能性。
  2. 网站安全防护

    • 黑名单:对于频繁发起恶意请求、爬取数据、DDoS攻击等活动的IP,将其加入黑名单以限制其对网站的访问。
    • 白名单:如果只希望特定合作伙伴、内部员工或特定区域用户访问网站内容,则可通过白名单来限定合法访问者的范围。
  3. API接口保护

    • 对于对外提供的API接口,通过设置IP黑白名单,确保只有经过认证或信任的系统和客户端才能调用接口。

比如比较容易被盗刷的短信接口、文件接口,都需要添加IP黑白名单加以限制。

核心实现

获取客户端IP地址

 

java

复制代码

@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

 

java

复制代码

/** * 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,在接口上进行拦截,如果不满足配置的黑白名单,则抛出异常。

 

java

复制代码

/** * @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接口,添加接口拦截器

 

typescript

复制代码

/** * @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,该注解可以控制功能是否启用

 

less

复制代码

/** * @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

 

less

复制代码

@SpringBootApplication @EnableIpCheck public class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } }

新建测试接口

 

kotlin

复制代码

@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

 

bash

复制代码

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

  • 既配置白名单,也配置黑名单,需要既不在白名单,同时在黑名单里,才会拦截。

修改配置

 

yaml

复制代码

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

 

bash

复制代码

java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.

最后,可以结合配置中心,以便配置后立即生效。

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

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

相关文章

每日一练:LeeCode-200、岛屿数量【DFS递归+BFS队列】

给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设该网格的四条边…

特种兵旅游-扬州、南京Citywalk

一、扬州 Day01 西安咸阳机场->扬州泰州机场&#xff08;扬州地界但是离泰州也嘎嘎近&#xff09;->大运河博物馆&#xff08;需要提前预约&#xff01;&#xff09;&#xff08;超级震撼&#xff09; Day02 瘦西湖&#xff08;门票有点贵&#xff0c;但是蛮值得&#x…

【微服务】Eureka(服务注册,服务发现)

文章目录 1.基本介绍1.学前说明2.当前架构分析1.示意图2.问题分析 3.引出Eureka1.项目架构分析2.上图解读 2.创建单机版的Eureka1.创建 e-commerce-eureka-server-9001 子模块2.检查父子pom.xml1.子 pom.xml2.父 pom.xml 3.pom.xml 引入依赖4.application.yml 配置eureka服务5.…

【数据结构刷题专题】—— 二叉树

二叉树 二叉树刷题框架 二叉树的定义&#xff1a; struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(NULL), right(NULL); };1 二叉树的遍历方式 【1】前序遍历 class Solution { public:void traversal(TreeNode* node, vector&…

CI/CD 搭建jenkins基础测试环境构建项目(一)

Jenkins是一个开源的持续集成工具&#xff0c;可以帮助开发团队自动化构建、测试和部署他们的软件项目。通过Jenkins&#xff0c;开发团队可以实现快速、高效地交付软件&#xff0c;并及时发现和解决问题&#xff0c;从而提高团队的生产力和软件质量。持续集成/持续交付&#x…

后端常问面经之操作系统

请简要描述线程与进程的关系,区别及优缺点&#xff1f; 本质区别&#xff1a;进程是操作系统资源分配的基本单位&#xff0c;而线程是任务调度和执行的基本单位 在开销方面&#xff1a;每个进程都有独立的代码和数据空间&#xff08;程序上下文&#xff09;&#xff0c;程序之…

计算机网络面试题二

1.什么是计算机网络拓扑结构&#xff1f;计算机网络拓扑结构有哪五种基本形式&#xff1f; 计算机网络的拓扑结构是指网络中各个结点之间相互连接的几何形式。计算机网络拓扑结构的基本形式有&#xff1a;总线型网络、环形网络、星形网络、树形网络、网状网络。 2.什么是协议…

【【嵌入式开发 Linux 常用命令系列 1.4 -- grep -Ev 反向搜索】

文章目录 grep 的反向搜索 grep 的反向搜索 如果想从文件 a 中过滤掉包含字符串 “dash” 或 “tom” 的行&#xff0c;并将剩余的行写入到文件 b&#xff0c;可以使用 grep 命令配合正则表达式。使用 grep 的 -E 选项允许你指定一个扩展的正则表达式&#xff0c;而 -v 选项使…

C# 中Linq并行查询AsParallel 方法与Stopwatch类的理解与使用

AsParallel 是 C# 中的一个方法&#xff0c;它属于 System.Linq 命名空间下的 ParallelEnumerable 类。这个方法用于启用查询的并行执行。当你对一个数据集合执行 LINQ 查询时&#xff0c;通常这些查询是按顺序执行的。但是&#xff0c;当你调用 AsParallel 方法后&#xff0c;…

sql中添加数据的命令

SQL&#xff0c;全称是结构化查询语言&#xff08;Structured Query Language&#xff09;&#xff0c;是一种特殊目的的编程语言&#xff0c;主要用于数据库查询和程序设计。它允许用户存取数据、查询、更新和管理关系数据库系统。SQL是高级的非过程化编程语言&#xff0c;允许…

详解多模态 AI

2022 年 11 月&#xff0c;OpenAI 推出了 ChatGPT。它只用了几天时间就以其前所未有的能力席卷了世界。生成式人工智能革命已经开始&#xff0c;每个人都在问同一个问题&#xff1a;下一步是什么&#xff1f; 当时&#xff0c;ChatGPT 和许多其他由大型语言模型 &#xff08;L…

Acer宏碁暗影骑士擎AN515-58笔记本电脑工厂模式原厂Win11系统ISO镜像安装包下载

宏基AN515-58原装出厂OEM预装Windows11系统工厂包&#xff0c;恢复出厂时开箱状态一模一样&#xff0c;带恢复还原功能 链接&#xff1a;https://pan.baidu.com/s/1iCVSYtList-hPqbyTyaRqQ?pwdt2gw 提取码&#xff1a;t2gw 宏基原装系统自带所有驱动、NITROSENSE风扇键盘灯…

4.2 循环语句loop,等差数列求和

汇编语言 1. 循环语句loop loop指令的格式是&#xff1a;loop 标号&#xff0c;CPU执行loop指令的时候&#xff0c;要进行两部操作 cx cx - 1;判断cx中的值&#xff0c;不为0则转至标号处执行程序&#xff0c;如果为0则向下执行 循环使用loop来实现&#xff0c;循环次数存…

一文看尽Mac上运行Windows的所有可能:虚拟机、云电脑 更多

需求背景 大学期间我一直用的是windows,经历了从wind8到wind11的时代。 21年我转型干了产品,拿着surface、Thinkpad办公,生产力跟身边MacBook的同事相比大打折扣;于是我入手了一款macbook。一用上就回不了头,苹果的妙控键盘、触摸板真的是梦幻的办公组合。我本人也没有在…

04、Lua 数据类型

Lua 数据类型 Lua 数据类型nil&#xff08;空&#xff09;boolean&#xff08;布尔&#xff09;number&#xff08;数字&#xff09;string&#xff08;字符串&#xff09;table&#xff08;表&#xff09;function&#xff08;函数&#xff09;thread&#xff08;线程&#xf…

目标跟踪研究

我以前以为是靠高帧率实现目标识别的,现在才意识到目标跟踪也扮演着重要的角色 目录 1.目标跟踪问题简述2. 目标追踪综述3.目标跟踪-匈牙利匹配4.目标追踪-距离计算5.待续1.目标跟踪问题简述 目标跟踪 要实现跟踪,也就是需要了解前后帧之间的关系,那自然需要一个队列来保存…

vue中动态路由是什么该如何实现

在 Vue 中&#xff0c;动态路由是指根据不同的参数或条件&#xff0c;生成不同的路由配置。实现动态路由可以通过以下步骤&#xff1a; 1. **定义动态路由规则**&#xff1a; - 在路由配置文件&#xff08;通常是 router/index.js&#xff09;中&#xff0c;使用路由的 pat…

Typora结合PicGo + Github搭建个人图床

目录 一 、GitHub仓库设置 1、新建仓库 2、创建Token 并复制保存 二、PicGo客户端配置 1、下载 & 安装 2、配置图床 三、Typora配置 一 、GitHub仓库设置 1、新建仓库 点击主页右上角的 号创建 New repository 填写仓库信息 2、创建Token 并复制保存 点击右上角…

神经网络深度学习梯度下降算法优化

【神经网络与深度学习】以最通俗易懂的角度解读[梯度下降法及其优化算法]&#xff0c;这一篇就足够&#xff08;很全很详细&#xff09;_梯度下降在神经网络中的作用及概念-CSDN博客 https://blog.51cto.com/u_15162069/2761936 梯度下降数学原理

JavaSE(上)-Day10

JavaSE&#xff08;上&#xff09;-Day10 多态多态中成员变量的调用多态中成员方法的调用多态的优缺点 包final关键字权限访问修饰符代码块 多态 什么是多态&#xff1a;对象的多种形态多态的前提是&#xff1a;有继承或实现关系&#xff1b;子类重写父类的方法&#xff0c;父…