Laravel 跨域解决方案
我们在用 laravel
进行开发的时候,特别是前后端完全分离的时候,由于前端项目运行在自己机器的指定端口(也可能是其他人的机器) , 例如 localhost:8000
, 而 laravel
程序又运行在另一个端口,这样就跨域了,而由于浏览器的同源策略,跨域请求是非法的。其实这个问题很好解决,只需要添加一个中间件就可以了。
- 新建一个中间件
php artisan make:middleware EnableCrossRequestMiddleware
- 书写中间件内容
<?php namespace App\Http\Middleware; use Closure; class EnableCrossRequestMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); $origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : ''; $allow_origin = [ 'http://localhost:8000', ]; if (in_array($origin, $allow_origin)) { $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'); $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; } }
$allow_origin
数组变量就是你允许跨域的列表了,可自行修改。 - 然后在内核文件注册该中间件
protected $middleware = [ // more App\Http\Middleware\EnableCrossRequestMiddleware::class, ];
在
App\Http\Kernel
类的$middleware
属性添加,这里注册的中间件属于全局中间件
。
然后你就会发现前端页面已经可以发送跨域请求了。
会多出一次
method
为options
的请求是正常的,因为浏览器要先判断该服务器是否允许该跨域请求。
补充
有时候返回的不是 laravel 的 response 对象而是 Symfony 的 response,所以会报 $response->header 方法找不到,所以添加 header 的方法要简单改一下, 可以拼好一个数组直接调用一次,我这里是懒得改了。
$response->headers->add(['Access-Control-Allow-Origin' => $origin]);
$response->headers->add(['Access-Control-Allow-Headers' => 'Origin, Content-Type, Cookie,X-CSRF-TOKEN, Accept,Authorization']);
$response->headers->add(['Access-Control-Expose-Headers' => 'Authorization,authenticated']);
$response->headers->add(['Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, OPTIONS']);
$response->headers->add(['Access-Control-Allow-Credentials' => 'true']);
补充 2
另外需要注意的是,lumen 框架直接添加这个 中间件是不行的,妥妥的报 options
路由找不到,因为 lumen
用的是 fast-route
路由组件,跟 laravel
的不是同一个,laravel
可以是因为它帮你做了这件事 ,所以我们要自己注册一个 options路由
, 大致代码如下:
$app->router->group([
'prefix' => 'api',
'middleware' => ['cross','api'],
], function ($router) {
$router->options('/{path:.*}', function ($path) {});
require __DIR__ . '/../routes/api.php';
});
bootstrap/app.php
本作品采用《CC 协议》,转载必须注明作者和本文链接
高认可度评论:
如果是跨域,是作者提到的思路,需要给后端api项目添加cors middleware就行;
如果前端项目html/后端项目api在同一个域下
(如前端是localhost:8080,后端是localhost:8080/api/v1/accounts)
,可以通过nginx配置(/var/www/html是前端项目,/var/www/api是后端项目),然后启动nginx进程/php-fpm进程就能work了(这里nginx.conf把请求转发到9000端口,php-fpm.conf中也得监听这个9000端口,可以修改端口但需要保持统一):关于对nginx的配置指令感兴趣,可以参考nginx.org官网学习下就行。
好像没有这个说法吧?
@Littlesqx 有的,https://developer.mozilla.org/zh-CN/docs/W...
@Littlesqx 这个有的,看楼上。
@cky 正解。
改nginx 配置的路过
@tanjibo 不够灵活。
确实是会出现 options 请求的 感谢楼主
@GhostCoder :star:
用这个也可以啊 老药 :smile: barryvdh/laravel-cors
@godruoyi 嗯,我看过这个包。不过也要知道一下怎么实现嘛,而且我觉得自己写个中间件更加灵活可控。
可以弄成一扩展包
@Summer 哈哈,这个别人封装过了,我就不操这个心了。
我在使用中 因为用的passport认证,
所以这一项
$response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
最后一个参数接上了 ,X-Requested-With 这里标记一下
@魏文豪 不论需要添加什么请求头,都可以在这个中间件实现,稍微改动一下就可以了。
666
以前也写过这个中间件,但是没搞出来怎么允许多个域名,点个赞
我还是不懂得是, barryvdh/laravel-cors这个包,有什么其他功能吗?或者是做的更完善?只写一个中间件这种会不会有什么漏洞
@禹声 你可以去看看他的包用什么实现的。然而我再简单封装一下然后弄成一个包,不也是一个扩展包吗?
@里暮色中 哈哈,好的好的,因为我看很多人用这个包
如果是跨域,是作者提到的思路,需要给后端api项目添加cors middleware就行;
如果前端项目html/后端项目api在同一个域下
(如前端是localhost:8080,后端是localhost:8080/api/v1/accounts)
,可以通过nginx配置(/var/www/html是前端项目,/var/www/api是后端项目),然后启动nginx进程/php-fpm进程就能work了(这里nginx.conf把请求转发到9000端口,php-fpm.conf中也得监听这个9000端口,可以修改端口但需要保持统一):关于对nginx的配置指令感兴趣,可以参考nginx.org官网学习下就行。
@禹声 嗯有现成的就用现成的也挺好的。
@lx1036 nginx 确实可以实现,但是上面说过的不灵活的问题确实也比较现实,不太建议这种方案。
@里暮色中 修改nginx配置并没有不灵活,相反很灵活啊。给API项目实现跨域,这是个需求,并不是所有后端API项目都需要实现跨域需求。
@lx1036 那要是允许跨域列表需要后台管理员动态增加呢?
@cjjian 晚上好
@里暮色中 哈,调试中,发错。
@cjjian 那也晚上好~
会多出一次 method 为 options 的请求是正常的,因为浏览器要先判断该服务器是否允许该跨域请求
这个怎么解决呢??
@12pengpeng 这个不需要解决,因为这个不是bug.
在 Laravel 中跨域,如果想用 Laravel 程序实现,可以看看 分享:「新轮子」PHP CORS (Cross-origin resource sharing),解决 PHP 项... 这个中间件支持路由组,路由前缀等模式
我这里有个更好的解决方法,只修改中间件内容即可:
其他不变,这样跨域问题基本能解决
@Alwaysyouth 不建议直接用 header 函数。
我在一个laravel项目的前端模板中使用ajax去请求远程api(非该项目里的api接口),提示:
has been blocked by CORS policy: Request header field X-CSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response
这个需要怎么搞
@blinknull 服务器不允许你携带
X-CSRF-TOKEN
请求头,跟后端说加上Access-Control-Allow-Headers:X-CSRF-TOKEN
响应头。@96qbhy 我是在www.abc.com这个项目的前端模板中(laravel5.5项目),使用ajax,请求http://xxx.baidu.com(这个连接的服务器也是laravel项目,该项目我已经设置了跨域的一些设置,是可以跨域请求的),现在还是提示
has been blocked by CORS policy: Request header field X-CSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response
是不是www.abc.com这个项目也需要设置跨域的一些设置(我其实都已经做过一些设置了,还是提示这个)
@blinknull 允许跨域,但是不允许带那个请求头,看我上一个回答
@96qbhy 额就是这个原因,谢谢
@blinknull 不客气,多看错误提示,基本都能从错误提示找到解决办法。
请教问题,我用的dingo扩展包。然后按照楼主的方法,一直跨域失败。
必须设置header头才可以,不知道什么原因
@isalone 当然必须设置 header 头才可以啊,而且我不建议直接用下面这种写法
@isalone 当然必须设置 header 头才可以啊,而且我不建议直接用下面这种写法
难不成你们生产 环境不是用NGINX或者APACHE的???
直接在NGINX里面加不是更好,何必搞什么中间件
NGINX 加到SERVER里面:
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN';
add_header 'Access-Control-Expose-Headers' 'Authorization, authenticated';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, PUT, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
@oking 首先 ,我只希望给自己公司的项目跨域访问权限的话,这个
'Access-Control-Allow-Origin' '*'
就不符合需求了。其次,我希望可以后台动态的增删改查这个可跨域列表的话,就不适合写到 nginx 里面去了。最后,如果我有天改了需求,前端需要跨域的同时添加一个xx
请求头,是不是还得去 nginx 重启一下。@96qbhy 安全性考虑生产环境不会 'Access-Control-Allow-Origin' '*' , 是指定具体域名. 正常情况下这个域名列表不会经常变更, 一个公司就算N个项目,都是用一个域名,就算再添一个新域名,站运维nginx reload一下即可无缝重启WEB SERVER,现在运维工具都很先进,reload 一下所有web机器都是比较简单的事情.
@oking 嗯,有道理。所以我还是会选择中间件。
@qbhy 浏览器上一直显示response headers下面的
Access-Control-Allow-Origin: *
怎么办?
@coderWaHa 不需要怎么办啊,你返回这个请求头就会显示这个请求头啊
@qbhy
可是返回了啊
@qbhy 大佬 如果异常了不走中间件 返回500 前端还是显示跨域 这个怎么搞啊
@gotophp 这确实是个问题,建议在 Handler.php 里面处理。
我加了这个中间件后,并不生效啊。lv5.8版本会自动处理options请求,并且返回固定的header。这个处理cors的,注册在全局中间件里的逻辑,不生效。
为什么都喜欢用laravel中间件去实现跨域呢,用代理的方式不是更简单吗?
前后分离开发一般都可以用 proxyTable 做代理,生产环境下可以用nginx做反代。
用代理实现跨域还可以少一次options。
@yanthink 用中间件实现比较灵活,不需要代理,用代理还要多一层http消耗,得不偿失。
@qbhy
代理也是可配置的,没有不灵活。
跨域也有一次options请求,而且对于生产环境来说options请求是远程的,而且还得经过laravel框架,而代理可以是本地的,速度上来说代理应该会更快。
跨域请求得用绝对地址,代理可以用相对地址,换域名就可以更灵活。
配置上很多用户用中间件做跨域会遇到很多问题,而代理就不会。
@qbhy
举个例子:
前端代理
或者nginx反向代理
假设 origin 域名 和 target 域名都在同一台机器上,那么代理就不需要远程请求。
前端用origin相对地址请求,这时如果需要换目标域名,我们只需改代理域名即可,不需要修改前端代码。
增加一个动态的配置,只允许特定的站点
我现在遇到一个问题 ,为什么这个中间件只能放在 $middleware 中 而不能放在 api 下?
在前后端分离的应用中,需要使用CORS完成跨域访问。在CORS中发送非简单请求时,前端会发一个请求方式为OPTIONS的预请求,前端只有收到服务器对这个OPTIONS请求的正确响应,才会发送正常的请求,否则将抛出跨域相关的错误。
源码分析
这里的逻辑是:
首先根据当前HTTP方法(GET/POST/PUT/...)查找是否有匹配的路由,如果有(if(! is_null($route))条件成立),非常好,绑定后直接返回,继续此后的调用流程即可;
否则,根据$request的路由找到可能匹配的HTTP方法(即URL匹配,但是HTTP请求方式为其它品种的),如果count($others) > 0)条件成立,则继续进入$this->getRouteForMethods($request, $others);方法;
否则抛出NotFoundHttpException,即上述说到的404 NOT FOUND错误。
倘若走的是第2步,则跳转文件的234行,可看到函数逻辑为:
判断如果请求方式是OPTIONS,则返回状态码为200的正确响应(但是没有添加任何header信息),否则返回一个methodNotAllowed状态码为405的错误(即请求方式不允许的情况)。
因为 Laravel 这里对预请求做了处理,如果没有定义路由对 options 做处理 ,laravel 默认会直接返回 200 ,因此中间件组中定义的跨域中间件方法 EnableCrossRequestMiddleware 没有被触发之前就已经返回了相应的请求。
为了解决 options 的问题,这里在 api 路由表中增加如下代码:
跨域放在全局中就不会出现这个问题,全局中间件就是这么牛逼!
但是……不作不死的我就想把这个中间件放在对应的 api 组中,不然心里就是不爽,吃饭都不香……
https://packagist.org/packages/medz/cors
这个可以吗?
发现一个问题不知道怎么解决。是这样的: 加入日志权限问题或者其他问题导致请求接口时抛出500异常,导致 $response = $next($request) 后面的设置跨域的header头的代码没执行,没有返回正常的响应,前端就一直提示的是跨域失败,导致不能通过前端的显示定位问题。
@Tsingxu 可以try一下,欢迎加群交流 873213948
大佬,我想知道为啥在 index.php文件中加上这个 为啥get post没问题,put、delete却还是报跨域呢
我跨域都设置好了,有的 post 可以跨域,有的一直报这个错误,