Laravel Cors 中间件不适用于 POST 请求

Posted

技术标签:

【中文标题】Laravel Cors 中间件不适用于 POST 请求【英文标题】:Laravel Cors Middleware not working with POST Request 【发布时间】:2019-11-24 07:48:49 【问题描述】:

所以我使用Laravel 5.8 作为APIReactJS 视图。

我已经创建了一个 'cors' 中间件,我在 Kernel.php 文件中注册了它,并且我在我正在使用的 api-routes 上使用它。我使用 GET 请求进行了测试,它可以工作,但是当我使用 POST 请求进行测试时,我得到了 cors 错误:

从源“http://localhost:3000”获取“http://localhost:8000/api/posts”的访问权限已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:没有“Access-Control-Allow-Origin”标头出现在请求的资源上。如果不透明的响应满足您的需求,请将请求的模式设置为“no-cors”以获取禁用 CORS 的资源。

所以我有我的 api.php ("/routes/api.php"):

Route::get('/posts', 'PostController@index')->middleware('cors');
Route::post('/posts', 'PostController@store')->middleware('cors');

我的 cors.php 中间件:

<?php

namespace App\Http\Middleware;

use Closure;

class Cors

  /**
   * Handle an incoming request.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  \Closure  $next
   * @return mixed
   */
  public function handle($request, Closure $next)
   
    return $next($request)
      ->header('Access-Control-Allow-Origin', '*')
      ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
      ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With, Application');
  

在我的 Kernel.php ("/app/Http/Kernel.php") 上,我使用 'cors' 中间件更新了 "$routeMiddleware" 数组

'cors' => \App\Http\Middleware\Cors::class, 

现在在我的 React 项目中,我的 api.js(我在其中编写了发出请求的代码):

// get Posts
export const getPosts = () => 
  return fetch('http://localhost:8000/api/posts')
    .then(res => res.json())
    .then(json => console.log(json))
    .catch(err => console.log(err));


// create new post
export const createPost = (post) => 

  return fetch('http://localhost:8000/api/posts',
  
    method: 'post',
    headers: 
      'Accept': 'application/json, text/plain, */*',
      'Content-Type': 'application/json'
    ,
    body: JSON.stringify(post)
  )
  .then(res => res.json())
  .then(res => console.log(res));

我不明白为什么当我尝试Get request 时一切正常,但是当我尝试Post Request 时,我得到了CORS error。有人遇到过这个问题吗?

【问题讨论】:

Access-Control-Allow-Origin: * 不被浏览器接受,您需要指定允许的域。没有更多的小丑* 【参考方案1】:

把你的中间件改成这个

<?php

namespace App\Http\Middleware;

use Closure;

class Cors

  /**
   * Handle an incoming request.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  \Closure  $next
   * @return mixed
   */
  public function handle($request, Closure $next)
  
    $domain = parse_url($_SERVER['HTTP_REFERER']);
    $host = '*';
    if (isset($domain['host'])) 
        $host = $domain['host'];
    
    return $next($request)
      ->header('Access-Control-Allow-Origin', $host)
      ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
      ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization,     X-Requested-With, Application');
  

但一旦投入生产,您需要通过环境变量来限制允许的主机。

您也可以只使用barryvdh/laravel-cors Link here

【讨论】:

感谢您的回复。我尝试了您的解决方案,但没有奏效。但是我尝试了 'barryvdh/laravel-cors' 解决方案,问题是,当我尝试 GET 请求时它正在工作(作为我的第一个 'cors' 中间件),但是当我尝试 POST 请求时,我得到了同样的错误: “CORS 策略已阻止从源 'localhost:3000' 获取 'localhost:8000/api/posts' 的访问权限:所请求的资源上不存在 'Access-Control-Allow-Origin' 标头。如果不透明的响应满足您的需求,将请求的模式设置为 'no-cors'..." 这真的很奇怪。 我发现了一些对我有帮助的东西:github.com/barryvdh/laravel-cors/issues/360 我改变了我从控制器返回的内容,奇怪的是,它现在正在发布请求。 (使用 'barryvdh/laravel-cors' 解决方案)! @JulioW。很高兴引导您找到适合您的解决方案。你的代码中有一些echo 吗?【参考方案2】:

为我解决这个问题的唯一方法是将 cors 中间件类放在 $middleware 数组的顶部 Kernel.php

protected $middleware = [
        \App\Http\Middleware\Cors::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,

    ];

这是我正在使用的 cors 中间件

<?php

namespace App\Http\Middleware;

use Closure;

class Cors

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    
        $response = $next($request);

        $response->header("Access-Control-Allow-Origin","*");
        $response->header("Access-Control-Allow-Credentials","true");
        $response->header("Access-Control-Max-Age","600");    // cache for 10 minutes

        $response->header("Access-Control-Allow-Methods","POST, GET, OPTIONS, DELETE, PUT"); //Make sure you remove those you do not want to support

        $response->header("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, X-Requested-With, Application");

        return $response;
    


希望有一天它会对某人有所帮助。

【讨论】:

【参考方案3】:

因此,当我在 api.php 中使用自己的 CORS 中间件而不使用水果蛋糕 (barryvdh/laravel-cors) 时,我遇到了同样的问题并花了数小时调试并找出问题所在。

经过数小时的调试和挫折后,我发现当您在组中使用中间件时,它不会立即得到应用。

laravel 如何匹配路由并“应用”中间件:

当您发送请求时,laravel 会读取 api.php 并“注册”所有路由和中间件并“记住”它们而不实际执行它们。在它“注册”所有这些之后(读取整个 api.php 文件),它执行一个函数,在该函数中输入来自 URL 的路径和请求中使用的 HTTP 方法,然后开始查找匹配的路由URL 和 HTTP 方法,找到一个后,它会执行该路由所在的那些中间件,然后执行控制器方法。

例如,当您向/api/posts 发送GET 请求时,您的代码会匹配资源方法index,然后执行中间件cors,因此它可以工作并从您的控制器返回数据。

为什么 POST、PUT、DELETE 和 PATCH 不适用于这种方法:

当你向/api/posts发送POSTPUTDELETEPATCH请求时,浏览器会先发送OPTIONS请求,所以laravel“注册”所有路由然后执行使用 URL 和 HTTP 方法的“匹配”(现在是 OPTIONS)。

但是没有路由有OPTIONSresources 也没有OPTIONS 方法,所以由于没有路由有OPTIONS 方法,所以laravel 不匹配任何东西,因此它不会执行您最终处理 OPTIONS 方法的那些中间件。

api.php 示例

Route::post('/posts', 'PostController@store')->middleware('cors');

Laravel 中的匹配函数:

“匹配”函数名为findRoute,位于vendor/laravel/framework/src/Illuminate/Routing/Router.php

/**
 * Find the route matching a given request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 */
protected function findRoute($request)

    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route);

    return $route;

当您使用error_log(json_encode($route), 0); 记录$route,然后发出GET 请求,然后查看错误日志,您可以看到成功的“匹配”并且它应用了cors 控制器: "uri":"api\/posts","methods":["GET","HEAD"],"action":"middleware":["cors"],"uses":"App\\Http\\Controllers\\PostController@index","controller":"App\\Http\\Controllers\\PostController@index","namespace":null,"prefix":"api","where":[],"isFallback":false,"controller":null,"defaults":[],"wheres":[],"parameters":[],"parameterNames":[],"computedMiddleware":null,"compiled":

但是当您发送POST 请求时,会发生这种情况: "uri":"api\/posts","methods":["OPTIONS"],"action":"uses":,"isFallback":false,"controller":null,"defaults":[],"wheres":[],"parameters":[],"parameterNames":[],"computedMiddleware":null,"compiled":

您可以看到实际上发送了OPTIONS 方法(因为浏览器首先发送OPTIONS 方法)并且没有匹配到任何中间件,因此PUT 请求失败并出现CORS 错误(@987654354 @)

总结及解决办法:

$routeMiddleware 数组中的中间件被应用之后 Laravel 成功地将路由与路径和 HTTP 方法匹配,因为不同的路由可以有不同的中间件。 $middleware 数组中的中间件(全局中间件)之前应用 Laravel 开始注册和匹配路由。

要解决这个问题,您必须$middleware 数组中使用一个全局中间件,该中间件处理OPTIONS 方法。您可以只使用处理它的Fruitcake,然后您可以在api.php 中使用您自己的 CORS 中间件,可以根据自己的喜好设置不同的标头(例如,不同路由/组/前缀的不同允许来源。

【讨论】:

【参考方案4】:

我通过使用 FormData 而不是 JSON.stringfy 解决了这个问题:

所以,我改变了:

let data = JSON.stringify(firstname:'John', familyname: 'Doe');

到:

let data = new FormData();
data.append('firstname','John');
data.append('lastname','Doe');

完整的代码是这样的:

fetch(YOUR_API_URL, 
    method: 'POST',
    body: data
    ).then(
    res =>res.json()
        .then(res => console.log(res))
    ).catch(err => console.log(err));

【讨论】:

以上是关于Laravel Cors 中间件不适用于 POST 请求的主要内容,如果未能解决你的问题,请参考以下文章

barryvdh/laravel-cors 不适用于我的路线

为啥 Laravel 中间件适用于单个路由而不适用于一组路由

为啥 CORS 似乎不适用于 POST?

Web Api 2 CORS 不适用于 POST

Flask & NextJS - CORS 不适用于 POST

Nginx CORS 不适用于 POST