PHP项目临时放行如何对白名单豁免限流

wen PHP项目 30

本文目录导读:

PHP项目临时放行如何对白名单豁免限流

  1. IP 白名单(最常用、最简单)
  2. Token/API Key 白名单(适用于 API 场景)
  3. User-Agent 白名单(适用于爬虫或特定客户端)
  4. 组合使用多种豁免条件(推荐)
  5. 配置文件管理临时白名单(运营人员使用)
  6. 最佳实践建议
  7. 简单的实现示例(完整的中间件)

在 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 动态管理,既能快速响应,又便于审计和自动过期清理。

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