核心是获取真实客户端IP并校验白名单:需正确配置TRUSTED_PROXIES,优先用$request->server('REMOTE_ADDR'),结合X-Forwarded-For校验;白名单应存数据库+缓存,中间件须注册于全局栈且位于TrustProxies之后、EncryptCookies之前。

中间件里怎么判断请求IP是否在白名单
核心是拿到真实客户端IP,再比对预设列表。Laravel默认的 $request->ip() 在有反向代理时可能返回代理IP而非真实IP,必须先确认 TRUSTED_PROXIES 配置正确,否则白名单形同虚设。
实操建议:
- 用
$request->server('REMOTE_ADDR')获取原始连接IP(绕过Laravel的IP解析逻辑),再结合X-Forwarded-For做校验,避免被伪造 - 白名单存配置文件(如
config/app.php新增'ip_whitelist' => ['192.168.1.100', '2001:db8::1']),别硬编码在中间件里 - 注意IPv6地址格式:
::1和0:0:0:0:0:0:0:1等价,但字符串比较不相等,要用filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)+ 标准化处理
为什么 app/Http/Middleware/CheckIpWhitelist.php 要放在全局中间件组之前
中间件执行顺序决定拦截时机。如果白名单中间件挂到 $middlewareGroups['web'] 里,但位置靠后,请求可能已触发Session启动、CSRF验证甚至路由绑定——这些操作本身就有开销,且部分攻击(如暴力探测)会在到达业务逻辑前就发起大量无效请求。
实操建议:
- 把中间件注册进
$middleware(全局栈),而非$middlewareGroups,确保它在所有请求最前端执行 - 在
App\Http\Kernel.php中,把它放在TrustProxies::class之后、EncryptCookies::class之前,既拿到可信IP,又避免后续中间件无谓执行 - 别依赖
RedirectIfAuthenticated这类中间件做权限控制——它们不处理未登录用户的IP过滤
遇到 403 Forbidden 却没走白名单中间件?检查这几个点
常见现象是配置写了、中间件也注册了,但IP被拦时返回的是Nginx/Apache的默认403,而不是Laravel抛出的异常或自定义响应,说明请求根本没进Laravel生命周期。
排查要点:
- 确认Web服务器(Nginx/Apache)没在入口层做过IP限制,比如Nginx的
deny all或云WAF规则优先级更高 - 检查
public/index.php是否被意外修改,导致Laravel启动流程跳过Kernel初始化 - 用
dd($request->ip(), $request->server('REMOTE_ADDR'))在中间件开头调试,看是否真被执行;如果没输出,说明中间件根本没注册或被条件跳过 - 开发环境开启
APP_DEBUG=true,否则中间件抛出的异常会被统一转成500,掩盖真实拦截逻辑
白名单要支持动态更新,但别每次请求都读配置文件
硬编码或每次从 config/app.php 读取白名单,看似简单,但Laravel配置是运行时缓存的,改完要 php artisan config:clear 才生效,不适合运维实时调整。
更务实的做法:
- 把白名单存在数据库表(如
ip_whitelists),用Cache::remember('ip_whitelist', 300, fn() => IpWhitelist::pluck('ip')->all())缓存5分钟,平衡实时性与性能 - 避免用
file_get_contents(config_path('whitelist.json'))—— 文件IO在高并发下易成瓶颈,且无缓存机制 - 如果必须用文件,用
apcu_fetch('ip_whitelist')做进程内缓存,比Redis更快,但需确保APCu启用且未被OPcache干扰
真正麻烦的是多机部署时缓存一致性:哪怕只改一条IP,也要让所有PHP-FPM worker同步刷新,这时候文件方案反而比缓存更可控——但得接受重启服务或手动清缓存的成本。










