本文目录导读:

在 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 左右。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播