最稳做法是使用fruitcake/laravel-cors包并正确配置supports_credentials与allowed_origins;手写中间件易漏options预检处理,且带凭证时access-control-allow-origin不能为*,必须精确匹配源。

直接上结论:用 fruitcake/laravel-cors 包 + 正确配置 supports_credentials 和 allowed_origins 组合,是最稳、最省事、也最符合标准的做法。手写中间件容易漏掉 OPTIONS 预检处理,尤其在带 Cookie 或 Token 的场景下必出错。
为什么不能只在中间件里 header('Access-Control-Allow-Origin', '*')
浏览器对“带凭证”的跨域请求(比如前端设置了 withCredentials: true,或发了含 Cookie / Authorization 头的请求)有硬性限制:Access-Control-Allow-Origin 不能是 * —— 必须是精确匹配的源(如 http://localhost:3000)。而手写中间件往往只写死一个 *,结果就是:开发时 GET 能通,一发 POST 或带 Token 就 403/被拦截。
更关键的是,手写中间件默认不处理 OPTIONS 预检请求。Laravel 收到 OPTIONS 请求时,会走路由匹配逻辑,如果没对应路由,就直接 404 或抛异常,根本不会进你的中间件 —— 所以你看到的“OPTIONS 请求没返回 CORS 头”,不是中间件没生效,而是它根本没运行。
正确做法是让中间件在预检阶段就介入并返回 200 + 正确头,而不是等 $next($request) 走完再加头。
fruitcake/laravel-cors 的 config/cors.php 关键项怎么填
打开 config/cors.php,重点调这三项:
-
paths:至少包含'api/*';如果要用 Sanctum 登录,必须加上'sanctum/csrf-cookie' -
allowed_origins:开发环境写具体地址,比如['http://localhost:3000', 'http://localhost:5173'];线上环境禁止用['*'],否则supports_credentials无效 -
supports_credentials:只要前端需要传 Cookie 或调用axios.defaults.withCredentials = true,就必须设为true;此时allowed_origins绝对不能含*
其他项可保持默认:allowed_methods 设 ['*'] 没问题;allowed_headers 设 ['*'] 在开发期够用,生产环境建议明确列出,如 ['Content-Type', 'Authorization', 'X-CSRF-TOKEN']。
中间件注册位置错了会导致 CORS 不生效
\Fruitcake\Cors\HandleCors::class 必须放在 app/Http/Kernel.php 的全局中间件数组 $middleware 最前面,不能塞在 $middlewareGroups['api'] 里 —— 因为预检请求(OPTIONS)通常不匹配任何命名路由组,只走全局中间件链。
错误示例(CORS 不生效):
protected $middlewareGroups = [
'api' => [
\Fruitcake\Cors\HandleCors::class,
// ...
],
];
正确写法(放最顶上):
protected $middleware = [
\Fruitcake\Cors\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
// ...
];
如果你用的是 Laravel 11+ 的函数式引导方式(bootstrap/app.php),那就用 ->withMiddleware(...) 注册,同样要确保它在管道最前。
调试时怎么看 CORS 头有没有生效
别只看浏览器控制台报错,直接看 Network 面板里实际响应头:
- 对普通请求(如 GET /api/user),检查响应是否含
Access-Control-Allow-Origin、Access-Control-Allow-Credentials - 对预检请求(OPTIONS /api/login),检查响应状态码是不是 200(不是 204!),且含
Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age - 如果 OPTIONS 返回 404,说明中间件根本没运行,先确认注册位置和
paths是否覆盖该路径 - 如果 OPTIONS 返回 200 但缺头,检查
allowed_origins是否匹配请求头里的Origin值(注意协议、端口、末尾斜杠都要一致)
最容易被忽略的一点:Laravel 的中间件只对匹配 paths 的请求生效。如果你访问的是 /v1/users,但 paths 只写了 ['api/*'],那 CORS 就不会加头 —— 这种路径不匹配问题,比配置写错更难排查。











