在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

相关文章

Java中线程池遇到父子任务示例及避坑

在Java中使用线程池可以有效地管理和调度线程,提高系统的并发处理能力。然而,当涉及到父子任务时,可能会遇到一些常见的Bug,特别是在子线程中查询数据并行处理时。本文将通过示例代码展示这些常见问...

java中异步任务的实现详解

在Java中实现异步任务是一种提高应用程序性能和响应性的常用技术。异步编程允许某些任务在等待其他任务完成时继续执行,从而避免了阻塞。本文将介绍几种在Java中实现异步任务的方法,并讨论它们的解决...

解密 ClassFinal 加密的 Java Jar包

ClassFinal 是一款java class文件安全加密工具,支持直接加密jar包或war包,无需修改任何项目代码,兼容spring-framework;可避免源码泄漏或字节码被反编译。要点...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件