本文目录导读:

在 PHP 项目中限制特定地区 IP 访问,通常有 3 种主流实现方式,以下是按“推荐度从高到低”排列的具体方案:
结合 CDN / WAF 服务(最推荐,生产环境首选)
原理:利用阿里云、腾讯云、Cloudflare 等提供的基于 GeoIP 的防火墙规则,在请求到达 PHP 服务器之前就拦截。
以阿里云 CDN / WAF 为例:
- 登录控制台 → CDN / WAF → 访问控制
- 添加“地区封禁”或“IP 黑名单”规则
- 选择允许/拒绝的国家或地区(如仅允许中国大陆)
优点:
- 不消耗 PHP 服务器资源
- 防御更底层,攻击无法穿透
- 更新维护简单
缺点:
- 需要额外付费(或使用 CDN 免费套餐)
- 对部分地区 IP 库准确度依赖服务商
使用 geoip2 扩展(PHP 内实现)
安装依赖 & 数据库
# 安装 PHP 扩展 pecl install geoip2 # 或直接通过 composer 安装纯 PHP 版(无需扩展) composer require geoip2/geoip2 # 下载 GeoLite2 免费 IP 数据库 # https://www.maxmind.com/en/accounts/current/geoip/downloads # 下载 GeoLite2-Country.mmdb 文件
代码实现
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// 初始化 Reader,指向你的 .mmdb 数据库文件
$reader = new Reader('/path/to/GeoLite2-Country.mmdb');
// 获取用户真实 IP(注意代理穿透)
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// 如果经过 CDN,需使用 CDN 传递的头部,如 HTTP_CF_CONNECTING_IP(Cloudflare)
// $ip = $_SERVER['HTTP_X_REAL_IP'] ?? $ip;
// 允许的地区代码(ISO 3166-1 alpha-2)
$allowedRegions = ['CN', 'HK', 'TW', 'MO']; // 中国(含港澳台)
try {
$record = $reader->country($ip);
$countryCode = $record->country->isoCode;
if (!in_array($countryCode, $allowedRegions)) {
// 拒绝访问
http_response_code(403);
die('Access Denied: Your region is not allowed.');
}
} catch (\GeoIp2\Exception\AddressNotFoundException $e) {
// 无法识别 IP(如内网 IP 或数据库过期),根据业务决定
http_response_code(403);
die('Invalid IP address.');
}
// 允许继续执行后续逻辑
echo "Welcome!";
?>
更新 IP 数据库(定时任务)
GeoIP 免费库每月更新一次,建议设置 crontab:
# 每月1号凌晨下载最新数据库 0 4 1 * * cd /path/to/your/project && curl -o GeoLite2-Country.mmdb https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz && tar -xzf ...
优点:
- 完全自主控制
- 免费(每月更新一次)
- 精度可接受
缺点:
- 需要维护数据库更新
- 每次请求增加约 5-15ms 查询时间(可加缓存优化)
基于 IP 库 + 内网/IP段白名单(轻量级)
适用于简单场景,例如仅限中国大陆某个省份或某几个 IP 段:
<?php
// 简单白名单 IP 段
$allowedIps = [
'123.0.0.0/8', // 示例:中国大陆某运营商
'36.0.0.0/8', // 另一个段
];
function ipInRange($ip, $cidr) {
list($subnet, $bits) = explode('/', $cidr);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
$allowed = false;
foreach ($allowedIps as $cidr) {
if (ipInRange($clientIp, $cidr)) {
$allowed = true;
break;
}
}
if (!$allowed) {
header('HTTP/1.1 403 Forbidden');
exit('Access Denied');
}
// 正常业务代码...
?>
缺点:
- 无法动态识别所有地区 IP
- 维护 IP 段表繁琐
- 只适合已知固定 IP 段场景
综合建议
| 场景 | 推荐方案 |
|---|---|
| 企业级生产环境 | CDN/WAF 层拦截 |
| 小型项目、预算有限 | GeoIP2(免费数据库) |
| 仅允许公司内/特定运营商 | IP 段白名单 |
| 需要高性能+精确控制 | 方案二 + Redis 缓存 GeoIP 结果 |
性能优化小技巧(针对方案二)
// 使用文件 cache 或 redis 缓存已查询的 IP
$cacheKey = "geoip:" . $ip;
$result = apcu_fetch($cacheKey);
if (!$result) {
$result = $reader->country($ip);
apcu_store($cacheKey, $result, 3600); // 缓存1小时
}
$countryCode = $result->country->isoCode;
这样同一 IP 重复访问时只需 0.1ms 左右。