在Spring Boot中实现支持多种IP格式的IP白名单过滤功能

在现代Web应用程序中,基于IP的访问控制是一种常见且有效的安全措施。本文将详细介绍如何在Spring Boot中实现IP白名单过滤功能,支持以下多种IP格式:

  • 单个IP地址:192.168.1.1
  • 通配符IP:192.168.2.*
  • CIDR表示法:10.0.0.1/29
  • IP范围:10.0.0.1-10.0.0.255
  • IPv6地址:2001:0db8::1
  • IPv6通配符:2001:0db8::*

项目初始化

1. 创建Spring Boot项目

使用您喜欢的方式(如Spring Initializr或IDE)创建一个新的Spring Boot项目,添加以下依赖:

  • Spring Web

2. 添加必要的依赖

为了处理IP地址的解析和匹配,我们需要添加以下依赖到pom.xml:

<dependencies>
    <!-- 其他依赖 -->

    <!-- Apache Commons Net用于处理CIDR和IP范围 -->
    <dependency>
        <groupId>commons-net</groupId>
        <artifactId>commons-net</artifactId>
        <version>3.8.0</version>
    </dependency>

    <!-- Guava用于处理IP地址 -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>31.1-jre</version>
    </dependency>
</dependencies>

请确保根据项目的实际情况调整版本号。

创建IP白名单过滤器

1. 定义白名单配置

在application.properties或application.yml中添加IP白名单配置,以便可以轻松地更改和管理。

使用application.properties

ip.whitelist=192.168.1.1,192.168.2.*,10.0.0.1/29,10.0.0.1-10.0.0.255,2001:0db8::1,2001:0db8::*

使用application.yml

ip:
  whitelist: 192.168.1.1,192.168.2.*,10.0.0.1/29,10.0.0.1-10.0.0.255,2001:0db8::1,2001:0db8::*

2. 解析和存储IP模式

创建一个类IpWhitelistUtil,用于解析白名单配置并检查IP地址是否在白名单中。

package com.example.demo.util;

import com.google.common.net.InetAddresses;
import org.apache.commons.net.util.SubnetUtils;

import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

public class IpWhitelistUtil {

    private List<IpPattern> patterns = new ArrayList<>();

    public IpWhitelistUtil(String whitelist) {
        String[] ips = whitelist.split(",");
        for (String ip : ips) {
            patterns.add(new IpPattern(ip.trim()));
        }
    }

    public boolean isWhitelisted(String ipAddress) {
        InetAddress inetAddress = InetAddresses.forString(ipAddress);
        for (IpPattern pattern : patterns) {
            if (pattern.matches(inetAddress)) {
                return true;
            }
        }
        return false;
    }
}

创建一个IpPattern类,用于表示不同的IP格式并实现匹配逻辑。

package com.example.demo.util;

import com.google.common.net.InetAddresses;
import org.apache.commons.net.util.SubnetUtils;

import java.math.BigInteger;
import java.net.InetAddress;
import java.util.regex.Pattern;

public class IpPattern {

    private String pattern;
    private Pattern regexPattern;
    private SubnetUtils subnetUtils;
    private InetAddress startIp;
    private InetAddress endIp;
    private boolean isSingleIp;
    private boolean isWildcard;
    private boolean isCidr;
    private boolean isRange;
    private boolean isIpv6Wildcard;

    public IpPattern(String pattern) {
        this.pattern = pattern;
        if (pattern.contains("*")) {
            if (pattern.contains(":")) {
                isIpv6Wildcard = true;
                String regex = pattern.replace("*", ".*");
                regexPattern = Pattern.compile(regex);
            } else {
                isWildcard = true;
                String regex = pattern.replace(".", "\\.").replace("*", "\\d{1,3}");
                regexPattern = Pattern.compile("^" + regex + "$");
            }
        } else if (pattern.contains("/")) {
            isCidr = true;
            subnetUtils = new SubnetUtils(pattern);
            subnetUtils.setInclusiveHostCount(true);
        } else if (pattern.contains("-")) {
            isRange = true;
            String[] parts = pattern.split("-");
            startIp = InetAddresses.forString(parts[0]);
            endIp = InetAddresses.forString(parts[1]);
        } else {
            isSingleIp = true;
            startIp = InetAddresses.forString(pattern);
        }
    }

    public boolean matches(InetAddress inetAddress) {
        if (isSingleIp) {
            return inetAddress.equals(startIp);
        } else if (isWildcard) {
            return regexPattern.matcher(inetAddress.getHostAddress()).matches();
        } else if (isIpv6Wildcard) {
            return regexPattern.matcher(inetAddress.getHostAddress()).matches();
        } else if (isCidr) {
            return subnetUtils.getInfo().isInRange(inetAddress.getHostAddress());
        } else if (isRange) {
            BigInteger ipVal = new BigInteger(1, inetAddress.getAddress());
            BigInteger startIpVal = new BigInteger(1, startIp.getAddress());
            BigInteger endIpVal = new BigInteger(1, endIp.getAddress());
            return ipVal.compareTo(startIpVal) >= 0 && ipVal.compareTo(endIpVal) <= 0;
        }
        return false;
    }
}

3. 实现过滤逻辑

创建一个IpWhitelistFilter类,实现javax.servlet.Filter接口。

package com.example.demo.filter;

import com.example.demo.util.IpWhitelistUtil;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class IpWhitelistFilter implements Filter {

    private IpWhitelistUtil ipWhitelistUtil;

    public IpWhitelistFilter(IpWhitelistUtil ipWhitelistUtil) {
        this.ipWhitelistUtil = ipWhitelistUtil;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑(如果需要)
    }

    @Override
    public void destroy() {
        // 清理逻辑(如果需要)
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String clientIp = getClientIp(httpRequest);

        if (ipWhitelistUtil.isWhitelisted(clientIp)) {
            chain.doFilter(request, response);
        } else {
            httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 处理多级代理的情况
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

注册过滤器

在Spring Boot应用中,我们需要注册自定义过滤器。创建一个配置类FilterConfig。

package com.example.demo.config;

import com.example.demo.filter.IpWhitelistFilter;
import com.example.demo.util.IpWhitelistUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Value("${ip.whitelist}")
    private String ipWhitelist;

    @Bean
    public FilterRegistrationBean<IpWhitelistFilter> ipWhitelistFilter() {
        FilterRegistrationBean<IpWhitelistFilter> registrationBean = new FilterRegistrationBean<>();
        IpWhitelistUtil ipWhitelistUtil = new IpWhitelistUtil(ipWhitelist);
        registrationBean.setFilter(new IpWhitelistFilter(ipWhitelistUtil));
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1); // 设置过滤器顺序
        return registrationBean;
    }
}

测试过滤器功能

1. 创建测试控制器

创建一个简单的控制器来测试过滤器。

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/test")
    public String test() {
        return "访问成功!";
    }
}

2. 运行应用程序

启动Spring Boot应用程序,并尝试从不同的IP地址访问/test端点。

  • 如果客户端IP在白名单中,将返回"访问成功!"。
  • 如果不在白名单中,将返回403 Forbidden错误。

3. 使用工具测试

您可以使用工具(如Postman或curl)并伪造X-Forwarded-For头来模拟不同的客户端IP。

示例命令:

curl -H "X-Forwarded-For: 192.168.1.1" http://localhost:8080/test

结论

通过本文的步骤,我们在Spring Boot中实现了一个支持多种IP格式的IP白名单过滤器。该过滤器可以识别单个IP地址、通配符、CIDR表示法、IP范围以及IPv6地址和通配符。这增强了应用程序的安全性,使其只能被授权的IP地址访问。

您可以将以上内容复制并保存为Markdown文件,以便参考和使用。

标签: Java

相关文章

如何使用Go编写跨平台组件并让Java或PHP调用

在现代软件开发中,跨语言调用是一个常见的需求。假设我们有一个用Go语言编写的组件,我们希望Java或PHP能够直接调用这个组件中对外提供的方法。为了实现这一目标,我们可以使用以下几种方法:1. ...

linux下或macOS中配置maven及加速

下载 Maven 压缩包:访问 Maven 官方下载页面,下载你需要的版本。解压压缩包:将下载的压缩包解压到你选择的目录,例如 /opt/maven:sudo mkdir -p /opt/mav...

javax.validation中数据验证的注解使用与示例

javax.validation 是 Java 提供的一个用于数据验证的库,其中定义了多种用于约束和验证数据的注解。下面对常用的验证注解类进行详细介绍,并提供对应的使用示例,每个注解都配有中文注...

编译最新的kkFileView并使用docker打包

kkFileView 是一个开源的文件预览服务,支持多种文件格式的在线预览。本文将详细介绍如何编译 kkFileView 的最新版本,并将其打包成 Docker 镜像。1. 环境准备在开始之前,...

Spring Boot 内置的常用工具类整理

Spring Boot 在核心库中提供了一系列常用的工具类,涵盖了断言、对象处理、集合操作、文件与资源处理、IO 流、反射和 AOP。这些工具类帮助开发者提高代码的简洁性和可维护性。本文将对这些...

JAVA+VUE的多国语言跨境电商外贸商城源码

多语言跨境电商外贸商城TikTok内嵌商城,商家入驻、一键铺货、一键提货 全开源完美运营海外版抖音TikTok商城系统源码,TikToK内嵌商城,跨境商城系统源码接在tiktok里面的商城。ti...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件