本文目录导读:

- IP 白名单(最常用、最简单)
- Token/API Key 白名单(适用于 API 场景)
- User-Agent 白名单(适用于爬虫或特定客户端)
- 组合使用多种豁免条件(推荐)
- 配置文件管理临时白名单(运营人员使用)
- 最佳实践建议
- 简单的实现示例(完整的中间件)
在 PHP 项目中对白名单进行限流豁免(临时放行),通常有几种常见的实现方案,以下是按推荐程度和复杂度排序的几种方法:
IP 白名单(最常用、最简单)
方案 A:在限流中间件中直接判断
<?php
// 限流中间件伪代码
class RateLimitMiddleware {
// 白名单 IP 列表(可以从配置文件、数据库或 Redis 读取)
private $whitelist = [
'127.0.0.1',
'10.0.0.1',
'192.168.1.100',
// 也可以支持 CIDR 格式
'10.0.0.0/8',
];
public function handle($request) {
$clientIp = $request->getClientIp();
// 先检查是否在白名单中
if ($this->isWhitelisted($clientIp)) {
return true; // 直接放行,不进行限流
}
// 正常限流逻辑
if ($this->exceedsLimit($request)) {
throw new RateLimitException('Too Many Requests');
}
return true;
}
private function isWhitelisted($ip) {
foreach ($this->whitelist as $whitelistItem) {
// 支持 CIDR 匹配
if (strpos($whitelistItem, '/') !== false) {
if ($this->ipInCidr($ip, $whitelistItem)) {
return true;
}
} else {
if ($ip === $whitelistItem) {
return true;
}
}
}
return false;
}
private function ipInCidr($ip, $cidr) {
list($subnet, $mask) = explode('/', $cidr);
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
$maskLong = -1 << (32 - $mask);
return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
}
}
方案 B:使用 Redis 存储动态白名单(更灵活)
<?php
class DynamicWhitelist {
private $redis;
private $whitelistKey = 'rate_limit:whitelist';
public function __construct($redis) {
$this->redis = $redis;
}
// 临时添加白名单(带过期时间)
public function addTemporaryWhitelist($ip, $ttl = 3600) {
$this->redis->sadd($this->whitelistKey, $ip);
$this->redis->expire($this->whitelistKey, $ttl); // 整个集合过期
// 或者使用单独的 key 带过期
$this->redis->setex("whitelist:{$ip}", $ttl, 1);
}
public function isWhitelisted($ip) {
// 检查动态白名单
if ($this->redis->exists("whitelist:{$ip}")) {
return true;
}
// 也支持静态白名单
return in_array($ip, $this->getStaticWhitelist());
}
// 移除白名单
public function removeWhitelist($ip) {
$this->redis->del("whitelist:{$ip}");
}
}
Token/API Key 白名单(适用于 API 场景)
<?php
class TokenWhitelist {
// 临时发放的豁免 Token
private $exemptTokens = [
'temp-token-abc123' => [
'expires_at' => 1700000000,
'limit' => 10000, // 可以设置特殊限额
],
'emergency-token-xyz789' => [
'expires_at' => 1700100000,
'unlimited' => true, // 完全不限流
]
];
public function checkToken($token) {
if (!isset($this->exemptTokens[$token])) {
return false;
}
$tokenData = $this->exemptTokens[$token];
// 检查过期
if (isset($tokenData['expires_at']) && time() > $tokenData['expires_at']) {
unset($this->exemptTokens[$token]); // 自动清理过期 token
return false;
}
return true;
}
}
User-Agent 白名单(适用于爬虫或特定客户端)
<?php
class UserAgentWhitelist {
private $whitelistPatterns = [
'/^Googlebot/i',
'/^Baiduspider/i',
'/^MyCompanyCrawler/i',
'/^Emergency\sClient/i', // 临时救急用
];
public function isWhitelisted($userAgent) {
foreach ($this->whitelistPatterns as $pattern) {
if (preg_match($pattern, $userAgent)) {
return true;
}
}
return false;
}
}
组合使用多种豁免条件(推荐)
<?php
class RateLimitExemptionManager {
private $whitelist;
private $tokenWhitelist;
private $userAgentWhitelist;
private $redis;
public function shouldExempt($request) {
// 1. 检查 IP 白名单
if ($this->whitelist->isWhitelisted($request->getClientIp())) {
return true;
}
// 2. 检查 API Token
$token = $request->getHeader('X-Exempt-Token');
if ($token && $this->tokenWhitelist->checkToken($token)) {
return true;
}
// 3. 检查 User-Agent
$ua = $request->getHeader('User-Agent');
if ($ua && $this->userAgentWhitelist->isWhitelisted($ua)) {
return true;
}
// 4. 检查 Cookie 或 Session(用于调试)
if ($request->getCookie('debug_mode') === 'whitelist') {
return true;
}
return false;
}
// 临时添加豁免
public function addTemporaryExemption($type, $value, $duration = 3600) {
switch ($type) {
case 'ip':
$this->redis->setex("exempt:ip:{$value}", $duration, 1);
break;
case 'token':
$this->redis->setex("exempt:token:{$value}", $duration, json_encode([
'limit' => 5000,
'created_at' => time()
]));
break;
}
}
}
配置文件管理临时白名单(运营人员使用)
config/whitelist.php
<?php
return [
// 永久白名单
'permanent' => [
'127.0.0.1',
'10.0.0.0/8',
],
// 临时白名单(带到期时间)
'temporary' => [
'192.168.1.50' => [
'expires_at' => '2024-12-31 23:59:59',
'note' => '运维人员临时调试'
],
'10.0.0.200' => [
'expires_at' => '+2 hours', // 支持相对时间
'note' => '紧急数据修复'
]
],
// 特殊规则
'special_routes' => [
'/api/health' => 'unlimited', // 健康检查端点不限流
'/api/emergency/**' => [ // 紧急接口特殊处理
'limit' => 1000,
'window' => 60
]
]
];
最佳实践建议
分层检查:IP → Token → User-Agent → 特殊路由
动态管理:通过 API 接口动态添加/移除临时白名单
// 管理接口示例
Route::post('/admin/whitelist/add', function() {
$ip = request('ip');
$duration = request('duration', 3600);
$reason = request('reason', '');
RateLimitExemptionManager::addTemporaryExemption('ip', $ip, $duration);
// 记录操作日志
Log::info("Added whitelist exemption", [
'ip' => $ip,
'duration' => $duration,
'reason' => $reason,
'operator' => auth()->user()->id
]);
return response()->json(['status' => 'ok']);
});
设置过期时间:临时放行一定要有 TTL(Time To Live),避免忘记清理
日志审计:记录所有白名单添加/移除操作
限制滥用:白名单本身也应该限流,防止被滥用
使用 Redis 或数据库:避免每次重启服务都丢失临时白名单
简单的实现示例(完整的中间件)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RateLimitWithWhitelist
{
public function handle(Request $request, Closure $next)
{
$ip = $request->ip();
// 1. 检查白名单
if ($this->isWhitelisted($ip)) {
return $next($request);
}
// 2. 正常限流(这里示例简单的计数器)
$key = "rate_limit:{$ip}:" . $request->path();
$count = Cache::get($key, 0);
if ($count >= 60) { // 每分钟60次
return response('Too Many Requests', 429);
}
Cache::put($key, $count + 1, 60);
return $next($request);
}
private function isWhitelisted($ip)
{
// 从缓存读取白名单(可以动态管理)
$whitelist = Cache::get('rate_limit_whitelist', []);
if (in_array($ip, $whitelist)) {
return true;
}
// 检查 CIDR
foreach ($whitelist as $item) {
if (strpos($item, '/') !== false) {
if ($this->ipInCidr($ip, $item)) {
return true;
}
}
}
return false;
}
}
选择哪种方案取决于你的项目架构和安全需求,最简单可靠的是 IP 白名单 + Redis 动态管理,既能快速响应,又便于审计和自动过期清理。