本文目录导读:

- 方法一:服务器层面(Nginx / Apache)【最推荐】
- 方法二:PHP 框架中间件/路由层(Laravel / ThinkPHP / Symfony)
- 方法三:Nginx 动态黑名单(基于 Map / Geographic 模块)
- 注意事项
- 总结 - 你该用哪种?
在 PHP 项目中配置访问黑白名单,通常可以从服务器层面(最推荐)、框架层面或代码层面来实现,以下是几种常见的安全配置方法,由优到次排列:
服务器层面(Nginx / Apache)【最推荐】
这种方法效率最高,不在 PHP 层面消耗资源,且不易被绕过。
Nginx 配置黑白名单
在 server 或 location 块中添加 allow 和 deny 指令。
示例:仅允许内网 + 特定 IP,禁止其他所有 IP(白名单模式)
server {
listen 80;
server_name yourdomain.com;
# 允许内网段
allow 192.168.1.0/24;
# 允许某个特定外网 IP
allow 8.8.8.8;
# 允许公网某个 IP 段
allow 203.0.113.0/24;
# 拒绝其他所有
deny all;
# ... 其余配置
location / {
try_files $uri /index.php?$args;
}
}
示例:禁止恶意 IP(黑名单模式)
server {
# 黑名单 IP(可以放在 http、server 或 location 块)
deny 192.0.2.1;
deny 198.51.100.0/24;
# 允许其他所有
allow all;
# ... 其余配置
}
提示:
deny all是白名单模式,allow all是黑名单模式。
Apache 配置黑白名单
通过 .htaccess 或主配置文件(httpd.conf / apache2.conf)的 <RequireAll>、RequireIp 指令(Apache 2.4+)。
白名单(仅允许特定 IP):
<Directory /var/www/html>
<RequireAll>
Require ip 192.168.1.0/24
Require ip 8.8.8.8
Require all denied
</RequireAll>
</Directory>
黑名单(禁止特定 IP):
<Directory /var/www/html>
<RequireAll>
Require ip not 192.0.2.1
Require ip not 198.51.100.0/24
Require all granted
</RequireAll>
</Directory>
PHP 框架中间件/路由层(Laravel / ThinkPHP / Symfony)
如果你希望基于业务逻辑(例如某个 API 只允许白名单 IP 调用),或者需要动态管理名单(比如从数据库读取),可以在框架层实现。
Laravel 中间件示例
创建一个中间件 IpMiddleware,通过 app/Http/Middleware/IpMiddleware.php 实现:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class IpMiddleware
{
public function handle(Request $request, Closure $next)
{
// 从数据库或配置中读取黑白名单
$whitelistIps = ['192.168.1.1', '10.0.0.0/24']; // 支持 CIDR
$clientIp = $request->ip();
if (!$this->ipInList($clientIp, $whitelistIps)) {
abort(403, 'Forbidden: IP not allowed');
}
return $next($request);
}
private function ipInList($ip, $list)
{
foreach ($list as $allowedIp) {
if (strpos($allowedIp, '/') !== false) {
// 处理 CIDR 子网
if ($this->ipInCIDR($ip, $allowedIp)) return true;
} else {
if ($ip === $allowedIp) return true;
}
}
return false;
}
private function ipInCIDR($ip, $cidr)
{
list($subnet, $bits) = explode('/', $cidr);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
return ($ip & $mask) === ($subnet & $mask);
}
}
然后在 app/Http/Kernel.php 中注册为全局中间件或路由中间件。
简易 PHP 原生过滤(没有框架)
在入口文件(如 index.php)顶部加入:
$whitelist = ['192.168.1.10', '10.0.0.%']; // 支持通配符
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
$allowed = false;
foreach ($whitelist as $ip) {
if (strpos($ip, '%') !== false) {
$pattern = str_replace('%', '\d+', $ip);
if (preg_match("/^$pattern$/", $clientIp)) {
$allowed = true;
break;
}
} elseif ($ip === $clientIp) {
$allowed = true;
break;
}
}
if (!$allowed) {
http_response_code(403);
die('Access Denied');
}
Nginx 动态黑名单(基于 Map / Geographic 模块)
对于大规模黑名单(如根据 GeoIP 禁止国家),可以使用 Nginx 的 geo 模块或 geoip2 模块,适合运维场景。
示例:根据国家禁止访问
# 需要安装 ngx_http_geoip_module 或 ngx_http_geoip2_module
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allow_country {
default 0;
CN 1;
US 1;
}
server {
if ($allow_country = 0) {
return 403;
}
# ... 其余配置
}
注意事项
- 优先级:服务器层面(Nginx/Apache)先于 PHP 代码运行,如果服务器配置了
deny all,PHP 代码根本不会接收到请求。 - 反向代理场景:PHP 服务器在 Nginx 后面(如使用 Nginx 代理到 PHP-FPM),
$_SERVER['REMOTE_ADDR']获取的是 Nginx 的内网 IP,需要在 Nginx 中设置proxy_set_header X-Forwarded-For $remote_addr;,并在 PHP 中读取$_SERVER['HTTP_X_FORWARDED_FOR'](注意伪造 XFF 的风险)。 - 动态与静态:静态规则(IP 段很少变化)放服务器层面,需要频繁更新(如根据数据库记录动态封禁 IP)则用 PHP 代码或防火墙脚本。
- 性能:服务器层面是毫秒级过滤,PHP 层面每次请求需执行代码,高并发时建议优先用服务器或防火墙(如 fail2ban + iptables)。
- 你该用哪种?
| 场景 | 推荐方案 |
|---|---|
| 后台管理只允许公司内网访问 | Nginx/Apache 白名单 |
| 需要根据数据库黑名单实时封禁 IP | PHP 中间件或入口脚本 |
| 跨国业务要封禁某些国家 | Nginx GeoIP + map 模块,或防火墙策略 |
| 临时屏蔽某个 IP 的攻击 | Nginx 直接在配置加 deny(reload 生效),或用 iptables/fail2ban |
根据你的实际环境(是否有 Nginx?是否用框架?名单是固定的还是动态变化的?)选择合适的层级即可。