最稳的选择是直接使用 fruitcake/laravel-cors,因其已内置预检处理、origin 白名单、凭据支持与响应头安全合并;自定义中间件仅在需动态校验 origin 或强耦合鉴权时必要,否则易漏 options 处理、凭据冲突等关键细节。

直接用 fruitcake/laravel-cors 是最稳的选择,自定义中间件只在极少数场景下有必要——比如你明确拒绝第三方包、必须动态判断 Origin、或要和现有鉴权逻辑强耦合。否则,手写中间件容易漏掉预检(OPTIONS)处理、凭据支持、响应头覆盖等关键细节,反而引入线上故障。
为什么手写 CORS 中间件常出问题
多数人写的 CorsMiddleware::handle() 只对主请求加头,但浏览器发起跨域请求前必先发一次 OPTIONS 预检。如果中间件没拦截并返回 200 + 正确的 Access-Control-Allow-* 头,前端就卡死在 pending 状态,控制台报错:Response to preflight request doesn't pass access control check。
常见疏漏点包括:
- 没调用
$request->isMethod('OPTIONS')分支做预检响应 - 硬编码
'Access-Control-Allow-Origin' => '*',却同时设了supports_credentials => true(这二者互斥) - 用
$response->header()覆盖已有头,而 Laravel 内部可能已由其他中间件设过同名头 - 没检查
$request->getSchemeAndHttpHost()是否在白名单里,直接放行所有 Origin
如何让自定义中间件真正可用
如果你确实需要自定义,核心是复刻 fruitcake/laravel-cors 的最小可行逻辑,而不是从零造轮子。重点补全三块:
- 预检请求拦截:在
handle()开头加if ($request->isMethod('OPTIONS')) { return response()->noContent(204)->withHeaders($this->getPreflightHeaders($request)); } - Origin 动态校验:不要写死
*,改用$origin = $request->header('Origin');,再匹配配置里的allowed_origins数组(支持https://*.example.com通配) - 响应头安全合并:用
$response->headers->set($key, $value, false)的第三个参数false避免覆盖,而非header()
示例关键片段:
protected function getPreflightHeaders($request)
{
$origin = $request->header('Origin');
$allowedOrigins = ['https://app.example.com', 'https://staging.example.com'];
$origin = in_array($origin, $allowedOrigins) ? $origin : null;
return [
'Access-Control-Allow-Origin' => $origin ?? '',
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type,Authorization,X-Requested-With',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '3600',
];
}
什么时候该坚持用 fruitcake/laravel-cors
绝大多数项目直接走标准方案更省心:
- 它已内置预检处理、Origin 白名单、凭据开关、头部合并逻辑
- 配置项
config/cors.php支持环境变量驱动,例如'allowed_origins' => explode(',', env('CORS_ORIGINS', '')) - 与 Laravel 的路由分组天然适配,比如只给
api/*加 CORS,不用改中间件代码 - 升级维护有保障——
fruitcake/laravel-cors已成为 Laravel 官方推荐方案,Laravel 9.2+ 更是直接集成其逻辑
强行自定义,往往只是把包里已验证过的逻辑再写一遍,还少测了边界 case。
调试时最容易被忽略的点
即使中间件代码看起来没问题,CORS 仍失败,大概率卡在这几个隐形环节:
-
TrustProxies中间件没配对:若 API 前有 Nginx 或 Cloudflare,$_SERVER['HTTP_ORIGIN']可能被清空,需在App\Http\Middleware\TrustProxies中正确设置$proxies和$headers - 缓存干扰:浏览器或 CDN 缓存了旧的 OPTIONS 响应,临时加个
Cache-Control: no-store排查 - 多个中间件重复设头:比如
TrustProxies或自定义日志中间件也写了Access-Control-Allow-Origin,导致 header 冲突 - HTTPS 混合内容:前端用
https://访问,但后端返回的Access-Control-Allow-Origin是http://,浏览器直接拒收
抓包看响应头是否真出现、是否被覆盖、是否匹配 Origin,比反复改中间件更有效。











