thinkphp api黑白名单必须在app/middleware.php全局中间件中实现,因请求生命周期为“接收→中间件→路由→控制器”,路由文件中加判断不会执行;需用$request->realip()获取真实ip,前缀匹配url,redis存黑名单以保障性能与安全。

ThinkPHP 实现 API 接口黑白名单,核心不在路由文件里写判断,而是在全局中间件中拦截——app/middleware.php 才是生效位置,route/app.php 或 route/api.php 里加逻辑根本不会执行。
为什么白名单/黑名单必须放在中间件里
ThinkPHP 的请求生命周期是:接收请求 → 执行全局中间件 → 匹配路由 → 调用控制器。路由匹配发生在中间件之后,所以你在路由定义里写 if ($request->ip() !== '192.168.1.100') die('forbidden'),这段代码压根不会运行。
- 中间件是唯一能「在路由前就决定放不放行」的环节
- 白名单要早于任何业务逻辑执行,否则攻击者可能绕过验证直接打到接口
- 黑名单同理,必须在控制器前拦截,避免无效请求触发数据库查询或日志写入
如何正确获取客户端真实 IP(防伪造)
Request::ip() 默认只读 $_SERVER['REMOTE_ADDR'],Nginx 反代后这个值是 127.0.0.1,不是用户真实 IP。直接信任 X-Forwarded-For 会被人伪造。
- 只在可信反向代理 IP 下才读取
X-Real-IP或X-Forwarded-For,例如 Nginx 部署在同一台机器时,可配置'trust_proxy' => ['127.0.0.1'] - 开启后改用
$request->realIP()(TP 6.1+),它内部已做头校验 - 没开
trust_proxy就硬取$_SERVER['X-Forwarded-For'],等于把黑名单形同虚设 - 测试时用
curl -H "X-Real-IP: 1.2.3.4" http://your-api.com/test模拟,别只靠本地127.0.0.1验证
白名单匹配该用前缀还是完整 URL?
用 str_starts_with($url, $prefix)(PHP 8.0+)或 strpos($url, $prefix) === 0 做前缀匹配,别用 in_array($url, $whitelist) 查完整 URL。
- 前缀匹配支持
/api/v1/、/admin/这类路径范围,维护成本低 - 完整 URL 匹配无法覆盖带参数的请求,比如
/api/user?id=1和/api/user算两个不同 URL - 不要用正则匹配路径,容易漏转义点号、斜杠,还拖慢性能
- 白名单规则建议从
config/route.php读取,而非硬编码在中间件里,上线后方便热更新
黑名单用 Redis SET 还是 PHP 数组?
小规模固定 IP(几百条以内)用 in_array($ip, $blacklist) 或 isset($blacklist[$ip]);动态增删或量大(万级+)必须用 Redis SET。
- Redis
SET的SISMEMBER是 O(1),查一个 IP 不依赖数据总量 - 别用
cache()->get('blacklist')返回数组再遍历——File 缓存驱动每次都要 IO 读取+反序列化 - 更别用 DB 查询,
Db::table('blacklist')->where('ip', $ip)->count()在高并发下直接拖垮数据库 - 添加/删除 IP 示例:
$redis->sAdd('blacklist:ips', '192.168.1.100')、$redis->sRem('blacklist:ips', '192.168.1.100')
最常被忽略的一点:中间件里忘了 return $next($request) 或返回了非响应对象(比如只 echo 'forbidden'),会导致后续流程中断但无报错,接口静默失败。务必确认所有分支都有明确返回值,且是 think\Response 实例或其子类(如 json())。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











