在实际项目开发中,很多场景下我们需要限制应用仅允许国内 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 功能稳定可靠运行。