ThinkPHP 8.0 跨域预检失败时,正确配置 CORS 中间件是解决问题的关键:需要拦截 OPTIONS 请求并返回 204 状态码,同时设置 Access-Control 响应头。该中间件必须放置在 middleware.php 数组的首位;当携带凭证时,禁止使用通配符 '*',应通过白名单校验 origin 并动态设置响应头。

当前端发起携带 Authorization 头或 Content-Type: application/json 的请求时,浏览器会自动发送一个 OPTIONS 预检请求。若服务器返回 405 状态码或无响应,则实际请求会被拦截。根源在于 ThinkPHP 8.0 默认未正确处理预检请求,或缺少必要的响应头信息。
创建并注册 CORS 中间件(ThinkPHP 8.0)
执行命令行生成中间件文件:php think make:middleware Cors。
打开 app/middleware/Cors.php,将 handle 方法替换为以下内容:
if ($request->isOptions()) {
return response('', 204)
->header('Access-Control-Allow-Origin', $request->header('origin') ?: '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-Requested-With')
->header('Access-Control-Allow-Credentials', 'true');
}
$response = $next($request);
$response->header('Access-Control-Allow-Origin', $request->header('origin') ?: '*');
$response->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->header('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-Requested-With');
$response->header('Access-Control-Allow-Credentials', 'true');
return $response;
关键:必须将 Cors::class 放置在 app/middleware.php 数组最前面,否则 SessionMiddleware 或 JWT 验证中间件可能提前输出响应,导致 headers already sent 错误。
处理带凭证(Cookie/Auth)的跨域请求
前端设定了 credentials: 'include',但浏览器会报错 “Credentials flag is true, but the 'Access-Control-Allow-Origin' value is not the literal '*'”——这是硬性限制,无法绕过。
第一步:禁止使用通配符 * 作为 Access-Control-Allow-Origin 的值。
第二步:动态读取 Origin 请求头,并仅允许白名单域名通过:
$origin = $request->header('origin');
$allowedOrigins = ['https://admin.example.com', 'https://localhost:3000'];
$origin = in_array($origin, $allowedOrigins) ? $origin : null;
第三步:仅当 $origin 不为 null 时才设置响应头,否则不返回任何 CORS 头——避免暴露敏感策略。
第四步:显式启用凭据支持:$response->header('Access-Control-Allow-Credentials', 'true');
验证 OPTIONS 预检请求是否生效的三种方法
方法一:用 curl 手动触发预检请求:
curl -I -X OPTIONS https://your-domain.com/api/v1/users
检查响应中是否包含 Access-Control-Allow-Origin 且状态码为 204。
方法二:在 Chrome 开发者工具 Network 标签页中,筛选 OPTIONS 请求,点击查看 Response Headers 区域。
如果看到 HTTP/2 404 或 HTTP/2 500,说明路由未匹配到 OPTIONS 方法,此问题并非中间件导致,而是路由层缺失处理路径。
方法三:临时在中间件 handle 开头加一行 file_put_contents('/tmp/cors.log', "OPTIONS hit\n", FILE_APPEND);,然后发起跨域请求,检查日志是否写入——能写入说明中间件已执行,失败点在于 header 设置或响应返回环节。
