根本原因是ThinkPHP6跨域中间件默认不拦截OPTIONS预检请求,导致其被路由或控制器捕获而返回404/405;必须在handle开头用$request->isOptions()判断并短路返回204响应,且需显式设置完整CORS头(Origin须精确匹配、含OPTIONS方法、包含前端实际Header、启用credentials时禁用*),中间件须注册为全局首位或准确挂载至路由分组,避免Nginx等服务器层冲突。

ThinkPHP6 跨域中间件对 OPTIONS 预检请求失效,根本原因在于:它默认不拦截、不响应 OPTIONS 请求,而是直接放行给后续逻辑——结果常被路由未匹配或控制器不存在捕获,返回 404 或 405 错误,浏览器就卡在预检阶段,前端拿不到响应头,报 “No 'Access-Control-Allow-Origin' header”。
必须拦截并短路 OPTIONS 请求
预检请求不能走正常业务流程。中间件 handle 方法开头就要判断并立即返回,不能等 $next($request) 执行完再处理。
- 用 $request->isOptions() 判断,别依赖 $_SERVER['REQUEST_METHOD'](在 Swoole/Workerman 环境下可能不准)
- 返回空响应 + 状态码 204(推荐)或 200,不能是 200 带 body,更不能是 404/500
- 示例写法:if ($request->isOptions()) { return response('', 204); }
响应头必须在短路后统一设置
OPTIONS 请求本身也要带 CORS 头,否则浏览器认为预检失败。这些头需显式写入 204 响应中,不能只加在正常请求上。
-
Access-Control-Allow-Origin:必须与前端 Origin 精确匹配(如
https://your-vue-app.com),禁用*(尤其开启 credentials 时) -
Access-Control-Allow-Methods:列出实际支持的方法,含
OPTIONS(如GET, POST, PUT, DELETE, OPTIONS) -
Access-Control-Allow-Headers:包含前端真实发送的头,常见有
Content-Type, Authorization, X-Requested-With - 若启用 cookie/token 认证,必须加
Access-Control-Allow-Credentials: true,且 origin 不能为通配符
中间件注册顺序和作用范围要准确
中间件没生效,90% 是注册位置不对。它必须是“最外层”的第一道中间件,才能确保所有请求(包括预检)都经过它。
立即学习“PHP免费学习笔记(深入)”;
- 在
app/middleware.php全局中间件数组中,把自定义 CORS 中间件放在首位 - 如果用了路由分组(如
Route::group('api', [...])),需确认该中间件已挂载到该分组,否则 /api/ 路径才生效,根路径或静态资源不走 - 禁用
header()直接输出——ThinkPHP 响应生命周期中,header()容易被覆盖;务必用$response->header()操作 Response 实例
生产环境必须白名单 Origin,不能用 *
开发时用 * 很方便,但一旦前端携带 cookie 或 Authorization,浏览器会直接拒绝,控制台明确提示 “Credentials flag is true, but 'Access-Control-Allow-Origin' value is not the literal '*'”。
- 配置 origin 白名单(如
['https://admin.example.com', 'http://localhost:8080']) - 动态匹配时,从
$request->header('origin')取值,校验是否在白名单内,再决定是否写入该 origin —— 不要无条件回传 origin,防 CSRF 风险 - Nginx/Apache 若也配了 CORS 头,要关闭,避免与 PHP 层冲突



















