ThinkPHP如何限制特定域名访问 ThinkPHP中间件实现域名拦截策略
在ThinkPHP6+中,通过中间件实现域名拦截,使用$request->domain()获取标准化域名,与配置文件白名单精确比对。中间件需置于全局列表首位,并对CLI请求兜底放行。拦截失败返回JSON格式的403响应,避免暴露服务器信息。
先说几个核心判断:在ThinkPHP 6+ 中间件里做域名拦截,最稳妥的做法是用 $request->domain() 拿到标准化域名(自动去端口、转小写、处理IP),然后跟配置文件里的白名单做严格比对。这个中间件必须塞到全局中间件列表的首位,还得单独兜底处理 CLI 请求那种没有 Host 头的情况。下面把每一步拆开聊。

中间件里怎么判断当前请求的域名
很多同学第一反应是用 $request->host(),这确实最直接,但坑也藏得深——它返回的是 HTTP 请求头 Host 的原始值,可能带着端口(比如 example.com:8080),也可能大小写混用。所以拦截前必须先标准化:自己用 parse_url() 或者正则把纯域名拎出来,才是稳妥的。
- 推荐直接用
$request->domain()(TP 6.1+ 开始支持),它会自动剥离端口、转小写、处理 IP 地址,返回一个干干净净的域名。 - 如果非要用
$request->header('host'),记得手动explode(':', ...)[0]去掉端口,否则localhost:8000和localhost会被当成两个域名,白名单根本配不过来。 - 本地开发时浏览器通常走
127.0.0.1或localhost,但部署后真实域名才生效——所以测试一定要在跟线上一致的环境里做,别在本地调通就觉得完事了。
如何配置白名单域名并做精确匹配
直接把域名列表硬编码在中间件里容易崩,更推荐写进配置文件(比如 config/app.php 里加一项 'allowed_domains' => ['api.example.com', 'admin.example.com']),中间件再统一读取比对。**别用 strpos() 或模糊匹配**,example.com 匹配 badexample.com 这种事太容易发生了。
- 用
in_array($request->domain(), config('app.allowed_domains'))做完全匹配,干净利落。 - 如果业务需要支持子域名通配(比如
*.example.com),那得自己写判断逻辑:str_ends_with($host, '.example.com') || $host === 'example.com'。 - 注意
www.example.com和example.com是两个域名,白名单里要么都写上,要么统一重定向到主域名,否则漏了一个就可能绕过去。
拦截失败时返回什么状态码和响应
直接 return response('', 403) 虽然能拦住,但前端收到个空响应很懵——返回 JSON 更适合前后端协作场景。另外,千万别返回 404,这不是资源不存在,而是权限拒绝,语义要清楚。
- 生产环境推荐
return json(['code' => 403, 'msg' => 'Forbidden domain'])->code(403)。 - 避免返回完整的错误堆栈(尤其调试模式开着的时候),防止暴露服务器信息。
- 如果项目还用了 CORS,记得在中间件里提前设置响应头(比如
$response->header('Access-Control-Allow-Origin', '*')),否则跨域请求可能卡在预检阶段就失败,让你误以为是拦截没生效。
为什么有些请求绕过了域名检查
大多数“漏网”案例不是代码写错,而是中间件注册位置不对。ThinkPHP 的中间件执行顺序非常关键:如果域名检查中间件放在路由之后(比如在 app/middleware.php 里写在 AllowCrossDomain::class 后面),那么某些静态资源或者未定义路由可能根本走不到你这步拦截。
- 必须把域名拦截中间件加到全局中间件数组的**最前面**(
app/middleware.php的$middleware第一项)。 - 确认没有在控制器或路由闭包里用
->middleware([])覆盖掉全局中间件。 - CLI 请求(比如命令行跑任务)没有
Host头,$request->domain()返回空字符串,白名单里没配空值就会被拦——需要单独打个补丁:if (empty($host)) { return $next($request); },让 CLI 正常放行。
域名拦截看着简单,真正上线后最容易栽在 Host 头解析不一致、中间件加载顺序、以及 CLI/HTTP 混用场景下的空 host 判断上。测试的时候别只刷浏览器,用 curl 带 -H 'Host: xxx' 手动模拟各种请求头,才能把隐藏的坑都踩出来。


































