Laravel 开启跨域功能:Nginx 配置、中间件方案与 OPTIONS 预检处理

晚来风
晚来风
发布时间:2026-08-25 | 60人浏览过
原创

前言

做前后端分离项目时,Laravel 接口一旦被浏览器判定为跨域,请求就可能直接被拦下;而真正棘手的地方,往往不是“能不能开跨域”,而是该放在 Nginx 统一处理,还是只对部分路由在 Laravel 中精细放行。本文沿着这条判断线梳理常见做法,并补上 axios 触发 OPTIONS 预检时的处理方式,方便你结合权限边界、灵活性和安全性快速落地。

做 API 开发时,跨域几乎是绕不开的问题,尤其是前后端分离后,接口和页面往往不在同一个域名下。Laravel 里处理跨域并不复杂,但关键在于先判断该把规则放在 Nginx,还是放在应用层;再看是否需要处理 axios 发起 POST 时连带出现的 OPTIONS 预检请求。下面按这两个判断路径展开,保留原有配置和代码,方便直接对照落地。

为什么会出现跨域限制

跨域本质上不是 Laravel 的问题,而是浏览器的同源策略在起作用。出于安全原因,浏览器会限制 Script 中的跨域请求。由于 XMLHttpRequest 遵循同源策略,所有使用 XMLHttpRequest 构造 HTTP 请求的应用,默认只能访问自己的域名。

这也是为什么前端页面明明能正常打开,但一调用另一个域名下的 API,就会在浏览器里报跨域错误。要让请求通过,服务端必须返回浏览器认可的跨域响应头。

先选方案:改 Nginx 还是改 Laravel

常见处理办法主要有两种,一种在 Web 服务器层解决,另一种在 PHP 应用层解决。两种都能生效,但适用场景不同。

Nginx 全局跨域与 Laravel 路由级跨域的对比编辑插画
跨域处理方案怎么选同样是处理跨域,服务器层适合统一放开,Laravel 中间件更适合按路由和来源做精细控制。

方案一:直接在 Nginx 中放开跨域

如果你有服务器配置权限,而且希望站点统一允许跨域,直接修改 Nginx 是最省事的做法。它的优点是改完即可全局生效,不需要动业务代码。

可以在站点配置节点下加入以下内容:

server
{
    listen       80;
    index index.html index.htm index.php;
    root  /www/public/;

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers "x-requested-with,Authorization,Accept, Origin, X-Requested-With, Content-Type, Last-Modified";
    add_header Access-Control-Allow-Methods GET,POST;
    add_header Access-Control-Max-Age 1728000;

    # ....
}

这种写法适合统一出口的接口服务,但它也有明显限制:一是控制粒度偏粗,通常是全局放开;二是只能按服务器配置处理,不容易精确到某一组路由或单个接口。对安全要求更细的项目,这种方式未必合适。

方案二:在 Laravel 里用中间件控制

如果你没有 Nginx 修改权限,或者只想让部分接口允许跨域,那么把逻辑放进 Laravel 中间件会更灵活。这样可以按路由组、接口前缀,甚至按来源域名做精细控制。

原文给出的思路是先读取请求头中的 HTTP_ORIGIN,再解析出来源主机与端口,只对白名单中的来源返回跨域头。中间件代码如下:

<?php
namespace AppHttpMiddleware;

use Closure;

class EnableCrossRequest
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest $request
     * @param  Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $origin     = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
        $originHost = parse_url($origin, PHP_URL_HOST);
        $originPort = parse_url($origin, PHP_URL_PORT);
        $originHost .= $originPort ? ':' . $originPort : '';

        // 允许跨域的域名 可以加在配置里
        $allowOriginHost = [
            'test.com',
        ];

        $response = $next($request);

        if (in_array($originHost, $allowOriginHost)) {
            $response->header('Access-Control-Allow-Origin', $origin);
            $response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN, Last-Modified');
            $response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
            $response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
            $response->header('Access-Control-Allow-Credentials', 'true');
        }

        return $response;
    }
}

这里有几个关键点值得注意:

  • Access-Control-Allow-Origin 没有直接写成 *,而是回写当前请求的来源地址,这更适合配合白名单使用。
  • Access-Control-Allow-Credentials 被设置为 true,说明这套配置考虑了带凭证的跨域请求场景。
  • 允许的方法里包含 GET, POST, PATCH, PUT, OPTIONS,为预检请求预留了空间。

如果你的项目需要按环境切换来源域名,文中的注释也已经给出方向:白名单可以进一步抽到配置文件中,而不是直接写死在中间件里。

中间件怎么注册并应用到路由

中间件写好之后,还要在 Laravel 里完成注册,否则路由不会实际使用这段跨域逻辑。

先在 app/Http/Kernel.php 中注册中间件组:


    protected $middlewareGroups = [
        // ....
        'api.cross' => [
            AppHttpMiddlewareEnableCrossRequest::class,
        ],
        // ....
    ];

然后,把它挂到需要跨域的路由组上:

Route::group([
    'prefix'     => 'api',
    'middleware' => ['api.cross'],
], function () {
    // 路由    
});

这种做法的价值在于,你不必把整站接口都暴露出去。只给 api 这类明确的接口组启用跨域,既能满足前端调用,又比全局放开更容易收敛风险。

axios 发送 POST,为什么会先变成 OPTIONS

很多人第一次排查跨域时会误以为接口被请求了两次,或者怀疑 Laravel 路由重复执行。实际上,跨域场景下只要命中了复杂请求条件,浏览器就可能先发一个 OPTIONS 请求做预检,用来确认服务端是否允许真正的跨域调用。

axios 复杂请求触发 OPTIONS 预检与正式请求的两段式流程插画
为什么会先收到 OPTIONS看到 OPTIONS 并不代表接口异常,很多时候只是浏览器在正式发送 POST 前先做一次预检。

所以,出现“先 OPTIONS,再真正的 POST”并不反常。原文也明确提到:有两次请求是完全正常的,但如果业务代码把预检请求也当成正式请求处理,就可能产生重复逻辑或无意义消耗。

一种直接的处理方式,是单独为 OPTIONS 增加路由,快速返回空响应:

Route::options('/{all}', function () {
    return response('');
})->where(['all' => '([a-zA-Z0-9-]|_|/)+']);

这样做的核心目标不是“取消”预检,而是让预检请求尽快结束,不进入真正的业务处理流程。对于前后端分离项目,这往往能减少接口排错时的干扰。

实际落地时该怎么选

如果你拥有服务器配置权限、接口范围也比较统一,Nginx 方案部署快、维护简单;如果你需要只开放部分接口,或者要按来源域名做细粒度判断,Laravel 中间件更适合。

可以按下面的思路判断:

  • 追求统一、全局生效:优先考虑 Nginx。
  • 需要只开放某些路由:优先考虑 Laravel 中间件。
  • 没有服务器权限:基本只能走应用层方案。
  • 前端使用 axios 且出现两次请求:先确认是否为正常的 OPTIONS 预检,再决定是否补一个快速响应路由。

从安全性和灵活性来看,把跨域能力精确绑定到具体路由,通常比整站一把放开更稳妥。对 Laravel 项目来说,中间件方案也更符合框架自身的组织方式,后续维护会更清楚。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表