核心是必须在中间件handle()开头拦截options并返回204响应,同时设置所有access-control-头;若写在$next($request)之后则头被丢弃,且带credentials时access-control-allow-origin不能为,须动态校验白名单域名。

ThinkPHP中间件处理跨域请求,核心就一条:必须主动拦截并响应 OPTIONS 预检请求,否则带 Authorization、Content-Type: application/json 或 credentials: 'include' 的请求根本发不出去——浏览器卡在 pending,控制台只报模糊的 “CORS error”,不提示真实原因。
为什么中间件里加了 header 还是 405 或没响应?
常见错误是把 response()->header() 写在 $next($request) 之后。这时响应体已生成,header 被丢弃;更糟的是,若前面有 SessionMiddleware 或 JWT 验证中间件提前输出内容,还会触发 “headers already sent” 错误。
-
if ($request->isOptions()) { return response('', 204); }必须放在handle()方法最开头 - 所有
Access-Control-*头必须随这个204响应一起写出,不能等$next()返回后再补 - 若用
header()函数(TP5 常见),它也必须在return response()之前调用,顺序错即失效
带 Cookie 或 Token 时,Access-Control-Allow-Origin 不能写 *
这是浏览器硬性限制,不是 ThinkPHP 问题。前端设了 credentials: 'include',后端却返回 Access-Control-Allow-Origin: *,响应直接被丢弃,连 200 都看不到。
- 必须动态读取
$request->header('origin'),再比对白名单数组 - 白名单要写全协议+域名+端口,例如
['https://admin.example.com', 'http://localhost:3000'],漏掉http://或端口都会失败 - 预检响应和主响应的
Access-Control-Allow-Origin值必须一致,不能一个写域名、一个写*
中间件注册顺序为什么必须放 app/middleware.php 数组最前面?
因为 CORS 中间件要成为整个请求生命周期的“第一道门”和“最后一道关”。它得在 Session、Auth、Log 等中间件之前拦截 OPTIONS,又得在它们之后给最终响应补头——但实际执行中,header() 必须在框架真正输出前完成,所以靠前注册 = 更早接管、更晚被覆盖。
- 错误位置示例:
[\think\middleware\SessionInit::class, \app\middleware\CorsMiddleware::class]→ Session 已启动输出,CORS header 写不进去 - 正确写法:
[\app\middleware\CorsMiddleware::class, \think\middleware\SessionInit::class, ...] - TP8 推荐直接用官方
\think\middleware\AllowCrossDomain::class,但也得放首位,否则无效
最易被忽略的一点:无论你用自定义中间件还是官方插件,只要前端带凭证(Cookie / Token),Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials: true 就必须成对出现且值严格匹配白名单;少一个判断、多一个星号,请求就静默失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











