Java 实现屏蔽国外 IP 访问的完整方案

在实际项目开发中,很多场景下我们需要限制应用仅允许国内 IP 访问,比如面向国内用户的业务系统、涉及敏感数据的后台管理平台等。本文将详细介绍如何在 Java 项目中实现屏蔽国外 IP 访问的功能,包含两种主流实现方案及注意事项。

一、需求分析与实现原理

1.1 核心需求

精准识别访问者 IP 所属国家 / 地区

对国外 IP 发起的请求进行拦截,返回指定提示信息

保证 IP 识别的准确性和性能,避免影响正常业务流程

支持 IP 库的定期更新,应对 IP 地址段的变化

1.2 实现原理

IP 地址本质是 32 位(IPv4)或 128 位(IPv6)的数字,每个国家 / 地区会分配到特定的 IP 地址段。实现屏蔽国外 IP 的核心逻辑是:

获取访问者的真实 IP 地址

将 IP 地址与国内 IP 地址段库进行匹配

若匹配失败(判定为国外 IP),触发拦截机制

若匹配成功(判定为国内 IP),允许请求继续执行

二、方案一:基于纯真 IP 库实现(本地化方案)

纯真 IP 库(QQWry.dat)是国内常用的 IP 地址库,包含丰富的 IP 段与地理位置映射关系,适合本地化部署,无需依赖第三方接口。

2.1 实现步骤

步骤 1:引入依赖

在 Maven 项目的 pom.xml 中添加 IP 解析工具依赖:

<dependency>

    <groupId>org.lionsoul</groupId>

    <artifactId>ip2region</artifactId>

    <version>2.6.4</version>

</dependency>

<!-- 用于处理HTTP请求获取IP -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>

步骤 2:编写 IP 工具类

import org.lionsoul.ip2region.xdb.Searcher;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

import java.io.InputStream;

import java.util.Objects;

public class IpUtils {

    // 加载ip2region数据库(建议项目启动时初始化)

    private static Searcher searcher;

    static {

        try {

            // 从classpath读取ip2region.xdb文件

            InputStream inputStream = IpUtils.class.getClassLoader().getResourceAsStream("ip2region.xdb");

            byte[] dbBin = new byte[inputStream.available()];

            inputStream.read(dbBin);

            searcher = Searcher.newWithBuffer(dbBin);

        } catch (IOException e) {

            throw new RuntimeException("初始化IP库失败", e);

        }

    }

    /**

    * 获取请求真实IP(处理代理、反向代理场景)

    */

    public static String getRealIp(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();

        }

        // 处理多IP场景(X-Forwarded-For可能包含多个IP,取第一个非unknown的)

        if (ip != null && ip.contains(",")) {

            String[] ipArray = ip.split(",");

            for (String s : ipArray) {

                if (!"unknown".equalsIgnoreCase(s.trim())) {

                    ip = s.trim();

                    break;

                }

            }

        }

        return ip;

    }

    /**

    * 判断IP是否为国内IP

    * @param ip IP地址(IPv4)

    * @return true:国内IP,false:国外IP

    */

    public static boolean isDomesticIp(String ip) {

        if (ip == null || ip.isEmpty()) {

            return false;

        }

        // 排除本地IP(如127.0.0.1、192.168.x.x等)

        if (isLocalIp(ip)) {

            return true;

        }

        try {

            // 搜索IP信息(格式:国家|区域|省份|城市|运营商)

            String ipInfo = searcher.search(ip);

            if (Objects.isNull(ipInfo)) {

                return false;

            }

            // 判断国家是否为"中国"

            String country = ipInfo.split("\\|")[0];

            return "中国".equals(country);

        } catch (Exception e) {

            // 异常时默认不拦截(避免影响正常访问,可根据业务调整)

            return true;

        }

    }

    /**

    * 判断是否为本地IP

    */

    private static boolean isLocalIp(String ip) {

        return ip.startsWith("127.")

                || ip.startsWith("192.168.")

                || ip.startsWith("10.")

                || ip.startsWith("172.16.") && ip.startsWith("172.31.");

    }

}

步骤 3:实现拦截器

通过 Spring MVC 的 HandlerInterceptor 拦截请求,判断 IP 是否合法:

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

public class IpFilterInterceptor implements HandlerInterceptor {

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String realIp = IpUtils.getRealIp(request);

        // 判断是否为国内IP

        if (!IpUtils.isDomesticIp(realIp)) {

            // 拦截国外IP请求,返回提示信息

            response.setContentType("application/json;charset=UTF-8");

            response.getWriter().write("{\"code\":403,\"msg\":\"当前IP不支持访问,请使用国内IP尝试\",\"ip\":\"" + realIp + "\"}");

            return false;

        }

        // 国内IP允许继续访问

        return true;

    }

}

步骤 4:注册拦截器

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

public class WebConfig wap.bjhzLgs.com WebMvcConfigurer {

    @Override

    public void addInterceptors(zhibo.Lnspv.com registry) {

        // 注册IP拦截器,可指定拦截/排除的路径

        registry.addInterceptor(new m.810g.com())

                .addPathPatterns("/**") // 拦截所有路径

                .excludePathPatterns("/error", "/static/**"); // 排除不需要拦截的路径

    }

}

2.2 优缺点分析

优点:本地化部署,无网络依赖,响应速度快,免费使用

缺点:IP 库需要定期手动更新(建议每月更新一次),不支持 IPv6(需选择支持 IPv6 的 IP 库)

三、方案二:基于云服务 API 实现(第三方依赖方案)

如果对 IP 识别的实时性和准确性要求较高,可使用阿里云、腾讯云等提供的 IP 地理位置查询 API,这类 API 会自动更新 IP 库,支持 IPv6。

3.1 实现步骤(以阿里云为例)

步骤 1:申请阿里云 API 密钥

登录阿里云控制台,进入「访问控制」创建 AccessKey

开通「IP 地址库」服务(部分服务免费,超出额度后收费)

步骤 2:编写 API 调用工具类

import com.aliyuncs.DefaultAcsClient;

import com.aliyuncs.IAcsClient;

import com.aliyuncs.exceptions.ClientException;

import com.aliyuncs.exceptions.ServerException;

import com.aliyuncs.ip2location.model.v20200101.DescribeIpv4LocationRequest;

import com.aliyuncs.ip2location.model.v20200101.DescribeIpv4LocationResponse;

import com.aliyuncs.profile.DefaultProfile;

public class AliyunIpUtils {

    // 替换为自己的AccessKey

    private static final String ACCESS_KEY_ID = "your-access-key-id";

    private static final String ACCESS_KEY_SECRET = "your-access-key-secret";

    private static final IAcsClient client;

    static {

        // 初始化阿里云客户端

        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", ACCESS_KEY_ID, ACCESS_KEY_SECRET);

        client = new DefaultAcsClient(profile);

    }

    /**

    * 通过阿里云API判断IP是否为国内IP

    */

    public static boolean isDomesticIp(String ip) {

        if (ip == null || ip.isEmpty() || IpUtils.isLocalIp(ip)) {

            return true;

        }

        DescribeIpv4LocationRequest request = new DescribeIpv4LocationRequest();

        request.setIp(ip);

        try {

            DescribeIpv4LocationResponse response = client.getAcsResponse(request);

            // 判断国家是否为"中国"

            return "中国".equals(response.getCountry());

        } catch (ServerException e) {

            // API服务异常时,建议默认允许访问(避免业务中断)

            e.printStackTrace();

            return true;

        } catch (ClientException e) {

            e.printStackTrace();

            return true;

        }

    }

}

步骤 3:修改拦截器

将拦截器中的IpUtils.isDomesticIp替换为AliyunIpUtils.isDomesticIp即可。

3.2 优缺点分析

优点:IP 库自动更新,准确性高,支持 IPv6,无需手动维护

缺点:依赖第三方服务,存在网络延迟,超出免费额度后产生费用,存在 API 调用失败风险(需做好降级处理)

四、注意事项

处理代理场景:必须通过X-Forwarded-For、Proxy-Client-IP等请求头获取真实 IP,避免代理服务器 IP 被误判

性能优化:本地化方案中,IP 库初始化应在项目启动时完成,避免每次请求重复加载;第三方 API 方案建议添加缓存(如 Redis),避免重复调用 API

降级策略:当 IP 库加载失败或 API 调用异常时,应默认允许访问(或根据业务需求处理),避免影响正常用户

IP 库更新:本地化方案需定期更新 IP 库(可通过定时任务自动下载最新版本),第三方 API 无需手动处理

IPv6 支持:若业务需要支持 IPv6,需选择支持 IPv6 的 IP 库或 API(如阿里云 IP 地址库支持 IPv6)

五、总结

两种方案各有适用场景:

若项目对成本敏感、无网络依赖需求,选择本地化方案(纯真 IP 库 /ip2region)

若项目对 IP 识别准确性和实时性要求高、预算充足,选择第三方 API 方案(阿里云 / 腾讯云)

实际开发中,可根据业务需求灵活选择,同时做好异常处理和性能优化,确保屏蔽国外 IP 功能稳定可靠运行。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容