中间件设置 Access-Control-Allow-Origin 未生效的主因是预检请求(OPTIONS)未进入中间件:ThinkPHP 默认不路由 OPTIONS 请求,需在中间件 handle() 中主动拦截并返回含完整 CORS 头的 200 响应,且 CORS 中间件须置于 app/middleware.php 数组首位以防被覆盖。

中间件里设置 Access-Control-Allow-Origin 为什么没生效
常见现象是:中间件写了响应头,但浏览器仍报 CORS error,尤其是预检请求(OPTIONS)直接 404 或被拦截。根本原因不是头没加,而是预检请求根本没走到你的中间件——它被框架路由或前置逻辑截断了。
ThinkPHP 默认对 OPTIONS 请求不做常规路由匹配,除非显式注册。必须在中间件中主动拦截并返回 200,且带上完整 CORS 头,否则浏览器卡在预检阶段。
- 在中间件
handle()开头加判断:if ($request->isOptions()) { return response('', 200)->header($corsHeaders); } -
$corsHeaders至少包含:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Credentials - 别漏掉
Access-Control-Max-Age,避免重复预检;值设为86400(24 小时)较稳妥
用 think-middleware-cors 包反而更难调
这个包默认只处理简单请求,对带 Authorization 或自定义 header 的请求无效,且不自动响应 OPTIONS。它把逻辑藏在底层,出问题时连日志都难定位。
真实场景中,你大概率要手动控制头内容(比如根据域名白名单动态设 Access-Control-Allow-Origin),而该包的配置项僵硬,origin 不支持回调函数,没法做条件判断。
立即学习“PHP免费学习笔记(深入)”;
- 删掉
think-middleware-cors,手写中间件更可控 - 若必须用,确认已发布到
app/middleware.php全局数组,并检查其allowOrigin是否写成*—— 带credentials时不能用通配符 - 它的
allowHeaders默认不包含Content-Type,但现代前端常发application/json,务必显式加上
Access-Control-Allow-Credentials: true 下的 origin 不能是 *
这是浏览器强制规范,不是 ThinkPHP 限制。一旦后端返回 Access-Control-Allow-Credentials: true,Access-Control-Allow-Origin 必须是具体域名(如 https://a.com),否则浏览器直接拒绝响应。
很多开发者卡在这里:前端开了 withCredentials: true,后端却还配着 *,错误信息只显示“CORS header ‘Access-Control-Allow-Origin’ cannot be wildcard”,不提 credentials 事。
- 前后端必须同步开关 credentials:前端设
withCredentials,后端才设Allow-Credentials: true,且 origin 必须精确匹配 - 开发环境常用
http://localhost:3000,生产是正式域名,建议用配置项或环境变量区分,别硬编码 - ThinkPHP 的
$request->domain()取不到跨域请求的原始源,得从$request->header('origin')读,再校验是否在白名单内
中间件顺序错位导致 CORS 头被覆盖
ThinkPHP 中间件执行顺序影响头输出。如果你把 CORS 中间件放在日志、鉴权或压缩中间件之后,后者可能重写或清空响应头,尤其 gzip 类中间件会提前触发输出缓冲。
典型表现:用 curl -I 看响应头正常,但浏览器里看不到 CORS 头——因为 gzip 中间件把头吞了或改了。
- CORS 中间件必须放在最顶层(
app/middleware.php数组第一个),确保最先执行 - 避免在控制器或模型里手动调
response()->header(),和中间件冲突 - 如果用了多层中间件(如 JWT 验证),确认它们没调
exit或die,否则后续中间件不执行,头自然不生效
跨域真正麻烦的从来不是加几行头,而是预检请求的静默失败、credentials 和 origin 的强耦合、以及中间件之间看不见的覆盖。这些地方一错,调试时根本看不到有效线索。



















