PHP项目地域访问如何限制指定地区IP

wen PHP项目 25

本文目录导读:

PHP项目地域访问如何限制指定地区IP

  1. 方案一:结合 CDN / WAF 服务(最推荐,生产环境首选)
  2. 方案二:使用 geoip2 扩展(PHP 内实现)
  3. 方案三:基于 IP 库 + 内网/IP段白名单(轻量级)
  4. 综合建议

在 PHP 项目中限制特定地区 IP 访问,通常有 3 种主流实现方式,以下是按“推荐度从高到低”排列的具体方案:


结合 CDN / WAF 服务(最推荐,生产环境首选)

原理:利用阿里云、腾讯云、Cloudflare 等提供的基于 GeoIP 的防火墙规则,在请求到达 PHP 服务器之前就拦截。

以阿里云 CDN / WAF 为例

  1. 登录控制台 → CDN / WAF → 访问控制
  2. 添加“地区封禁”或“IP 黑名单”规则
  3. 选择允许/拒绝的国家或地区(如仅允许中国大陆)

优点

  • 不消耗 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 左右。

抱歉,评论功能暂时关闭!