本文目录导读:

为PHP项目配置黑白名单可以基于IP地址、用户ID、请求来源、API密钥等不同维度,下面介绍几种常见且可落地的配置方法,按复杂度从低到高排列:
基于IP地址的黑白名单(最常用)
简单实现(硬编码或配置文件)
config/access.php
<?php
return [
// IP白名单
'whitelist' => [
'192.168.1.100',
'10.0.0.0/24', // 支持CIDR
'172.16.0.0-172.16.0.255' // 支持范围
],
// IP黑名单
'blacklist' => [
'203.0.113.0/24',
'198.51.100.50'
],
'mode' => 'whitelist' // 或 'blacklist'
];
中间件/入口检查
src/Middleware/AccessControl.php
<?php
namespace App\Middleware;
class AccessControl
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function handle($request, $next)
{
$ip = $request->getClientIp();
// 检查黑名单(黑名单优先于白名单)
if ($this->isBlacklisted($ip)) {
http_response_code(403);
exit('Access denied');
}
// 如果是白名单模式,检查是否在白名单中
if ($this->config['mode'] === 'whitelist') {
if (!$this->isWhitelisted($ip)) {
http_response_code(403);
exit('Access denied');
}
}
return $next($request);
}
private function isWhitelisted($ip)
{
return $this->matchIpList($ip, $this->config['whitelist']);
}
private function isBlacklisted($ip)
{
return $this->matchIpList($ip, $this->config['blacklist']);
}
private function matchIpList($ip, $list)
{
foreach ($list as $rule) {
if ($this->ipMatches($ip, $rule)) {
return true;
}
}
return false;
}
private function ipMatches($ip, $rule)
{
if (strpos($rule, '/')) {
// CIDR 匹配
list($subnet, $bits) = explode('/', $rule);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
return ($ip & $mask) === ($subnet & $mask);
} elseif (strpos($rule, '-')) {
// 范围匹配
list($start, $end) = explode('-', $rule);
$ip = ip2long($ip);
return $ip >= ip2long($start) && $ip <= ip2long($end);
} else {
// 精确匹配
return $ip === $rule;
}
}
}
使用示例(入口文件)
public/index.php
<?php
$accessConfig = require '../config/access.php';
$accessControl = new \App\Middleware\AccessControl($accessConfig);
// 假设使用某个框架的Request对象
$accessControl->handle($request, function($req) {
// 正常业务逻辑
echo "Welcome!";
});
基于数据库的动态黑白名单
适合需要后台管理、频繁更新规则的场景。
数据库结构
CREATE TABLE access_list (
id INT AUTO_INCREMENT PRIMARY KEY,
type ENUM('whitelist', 'blacklist') NOT NULL,
value VARCHAR(255) NOT NULL, -- IP、用户ID、域名等
match_type ENUM('ip', 'user_id', 'domain', 'path') DEFAULT 'ip',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
PHP 查询与匹配
<?php
class DynamicAccessControl
{
private $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function check($type, $value)
{
// 先检查黑名单
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM access_list
WHERE type = 'blacklist' AND value = ? AND match_type = ?"
);
$stmt->execute([$value, $type]);
if ($stmt->fetchColumn() > 0) {
return false;
}
// 如果是白名单模式,再检查白名单
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM access_list
WHERE type = 'whitelist' AND value = ? AND match_type = ?"
);
$stmt->execute([$value, $type]);
return $stmt->fetchColumn() > 0;
}
}
基于用户认证的黑白名单
适用于需要按用户级别控制访问的场景。
<?php
class UserAccessControl
{
const ROLE_WHITELIST = [
'admin' => ['*'], // 管理员全部允许
'editor' => ['/admin/*', '/api/*'], // 编辑可访问特定路径
'user' => ['/profile', '/content/*'] // 普通用户
];
const USER_BLACKLIST = [
1001, 1005, 2003 // 被封禁的用户ID
];
public function check($userId, $userRole, $requestPath)
{
// 黑名单用户
if (in_array($userId, self::USER_BLACKLIST)) {
http_response_code(403);
exit('Your account has been suspended');
}
// 白名单路径检查
if (!isset(self::ROLE_WHITELIST[$userRole])) {
return false;
}
$allowedPaths = self::ROLE_WHITELIST[$userRole];
foreach ($allowedPaths as $pattern) {
if (fnmatch($pattern, $requestPath)) {
return true;
}
}
return false;
}
}
基于Nginx/Apache的服务器级别控制(性能最优)
Nginx 配置
# IP白名单
location /admin {
allow 192.168.1.0/24;
allow 10.0.0.1;
deny all;
}
# 全局黑名单
location / {
deny 203.0.113.0/24;
deny 198.51.100.50;
# 其他正常配置
}
Apache .htaccess
# 白名单
<RequireAny>
Require ip 192.168.1.0/24
Require ip 10.0.0.1
</RequireAny>
# 黑名单
<RequireAll>
Require all granted
Require not ip 203.0.113.0/24
Require not ip 198.51.100.50
</RequireAll>
综合最佳实践
<?php
class AdvancedAccessControl
{
private $cache = [];
private $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function checkRequest($request)
{
// 1. 快速缓存检查(Redis/Memcached)
$cacheKey = 'acl:' . $request->getClientIp();
if (isset($this->cache[$cacheKey])) {
return $this->cache[$cacheKey];
}
// 2. 多维度检查
$checkResult = $this->multiDimensionalCheck($request);
// 3. 缓存结果(5分钟)
$this->cache[$cacheKey] = $checkResult;
// 4. 也可以写入Redis
// Redis::setex($cacheKey, 300, (int)$checkResult);
return $checkResult;
}
private function multiDimensionalCheck($request)
{
$ip = $request->getClientIp();
$userId = $request->getUserId();
$path = $request->getPath();
$userAgent = $request->getUserAgent();
$referer = $request->getReferer();
// 黑名单优先级最高
if ($this->isInBlacklist($ip, $userId, $path)) {
return false;
}
// 白名单模式检查
if (ACCESS_MODE === 'whitelist') {
return $this->isInWhitelist($ip, $userId, $path);
}
// 混合模式:API路径同时检查IP和UserID
if (strpos($path, '/api/') === 0) {
return $this->checkApiAccess($ip, $userId);
}
return true; // 默认允许
}
private function checkApiAccess($ip, $userId)
{
// 内部API只能由特定IP访问
if (strpos($path, '/api/internal/') === 0) {
return in_array($ip, INTERNAL_IPS);
}
// 公开API需要token中携带的白名单状态
return true;
}
}
关键注意事项
| 方面 | 建议 |
|---|---|
| 性能 | 生产环境优先用Nginx/Apache层控制,PHP层做次要校验 |
| 缓存 | 每次请求都查数据库会严重降低性能,建议用Redis缓存规则 |
| 日志 | 所有被拒绝的请求应当记录日志(IP、时间、路径、原因) |
| 监控 | 黑名单命中率异常升高可能是攻击,建议设置告警 |
| 更新 | 动态列表变动后要有机制清除缓存(如Redis Pub/Sub) |
简单场景:用方法1(配置文件+中间件)
需要管理后台:用方法2(数据库动态列表)
高并发:用方法4(Nginx层)+ PHP层二次校验
复杂权限:综合方法3和方法5
选择哪种方式取决于你的项目规模、并发量、以及是否需要动态管理规则。