ThinkPHP 6 的 CORS 必须通过自定义中间件实现,需手动处理 OPTIONS 预检、精确匹配 Origin 白名单并支持凭证,禁用调试模式下的宽松策略,避免 Nginx 与 PHP 层响应头冲突。

ThinkPHP 6 的 CORS 配置必须通过中间件,不是靠 header() 或 config/app.php 简单加字段
TP6 默认不内置跨域支持,CorsMiddleware 也不是框架自带中间件,直接在 config/cors.php 写白名单、改 header 都无效。真正起作用的只有「自定义中间件 + 请求前拦截」这一条路径。
常见错误现象:Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy,即使你在控制器里写了 header('Access-Control-Allow-Origin: http://localhost:3000'),依然报错——因为预检请求(OPTIONS)根本没走到控制器,被框架路由层提前拦截了。
- 必须新建中间件(如
app/middleware/CorsMiddleware.php),并在app/middleware.php或路由分组中注册 - 中间件
handle()方法里要显式处理OPTIONS请求并返回空响应,否则浏览器预检失败 -
Origin校验必须手动做,不能无脑设*(尤其带凭证时)
如何安全地实现接口访问白名单校验(含凭证场景)
带 withCredentials: true 的前端请求,后端 Access-Control-Allow-Origin 不能为 *,必须精确匹配 Origin,且要额外允许凭证头。白名单不能硬编码在中间件里,得可配置、可维护。
推荐做法:在 config/cors.php 定义白名单数组,中间件读取并比对 $request->header('Origin'):
立即学习“PHP免费学习笔记(深入)”;
// config/cors.php
return [
'allow_origins' => [
'https://admin.example.com',
'https://app.example.com',
'http://localhost:3000',
],
'allow_credentials' => true,
];
- 中间件中用
in_array($origin, $allowOrigins, true)判断,注意第三个参数必须为true(严格类型) - 若匹配成功,设置
Access-Control-Allow-Origin为该$origin值,同时设Access-Control-Allow-Credentials: true - 务必检查
$origin是否为空或非法协议(如data:、file:),避免开放给任意来源
为什么 TP6 的 think-middleware-cors 扩展不推荐直接用
社区有 topthink/think-middleware-cors 包,但它的默认行为是「全放行」:Access-Control-Allow-Origin: * 且不校验 Origin,也不处理凭证逻辑。线上环境开箱即用等于裸奔。
- 它把白名单写死在中间件构造函数里,无法动态加载配置,改个域名要重部署
- 对 OPTIONS 请求只简单返回 204,没做 Origin 检查,攻击者可伪造任意 Origin 发起预检
- 不兼容 TP6.3+ 的中间件生命周期变更,某些版本下会跳过
handle()直接走后续逻辑 - 真要用,至少得重写
getOrigin()方法,强制从配置读、加白名单过滤、拒绝空 Origin
生产环境必须关闭调试模式下的跨域宽松策略
TP6 在 APP_DEBUG = true 时,部分开发者会临时加「开发专用中间件」允许所有源,但很容易忘记上线前注释掉。更隐蔽的问题是:某些 IDE 插件或本地代理(如 vite dev server)会注入 Origin: null,而 TP6 的 Request::domain() 在 debug 模式下可能返回空字符串,导致白名单校验失效,意外放行。
- 上线前务必确认
env文件中APP_DEBUG=false,且config/cors.php中的allow_origins不含*或正则表达式 - 在中间件里加日志记录未匹配的
Origin(仅限测试环境),上线后禁用 - 如果用 Nginx 做反向代理,别在 Nginx 层加
add_header Access-Control-Allow-Origin—— 和 PHP 层冲突会导致响应头重复,浏览器直接拒收
白名单不是加几个域名就完事,关键在「每次请求都做 Origin 字符串精确比对」,以及「OPTIONS 预检必须由中间件终结,不能漏到控制器」。漏掉任一环,安全策略就形同虚设。



















