Laravel跨域处理
背景
在前后端分离的应用中,需要使用CORS完成跨域访问。在CORS中发送 非简单请求
时,前端会发一个请求方式为OPTIONS的预请求,前端只有收到服务器对这个OPTIONS请求的正确响应,才会发送正常的请求,否则将抛出跨域相关的错误。
跨域
可实现跨域的方式
- JSONP
- CORS
- Flash
- 服务器中转
比较常用的是 JSONP
和 CORS
,而后者相对前者来说有更方便实用:
JSONP
只能实现GET
请求,而CORS
支持所有类型的HTTP请求。- 使用
CORS
,开发者可以使用普通的XMLHttpRequest
发起请求和获得数据,比起JSONP
有更好的错误处理。
此文暂不介绍jsonp
CORS
CORS
是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源。而这种访问是被同源策略所禁止的。CORS
系统定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。
使用CORS
的方式非常简单,但是需要同时对前端和服务器端做相应处理。
客户端使用XmlHttpRequest发起Ajax请求,当前绝大部分浏览器已经支持CORS方式,且主流浏览器均提供了对跨域资源共享的支持。
如上所述,接着只需在服务端配置可允许跨域的header即可:
1 | # TestController.php |
之后测试发现 GET
网络请求正常,不会报跨域的错误,但是被调用方一直报调用方未登录的异常,定位到是调用方的cookie无法传输给被调用方,查到是需要手动在ajax中增加配置 withCredentials: true
XMLHttpRequest.withCredentials
跨域请求是否提供凭据信息(cookie、HTTP认证及客户端SSL证明等)
也可以简单的理解为,当前请求为跨域类型时是否在请求中协带cookie。
需要注意的是调用方配置了该参数后,被调用方的header中必须指定 Access-Control-Allow-Origin
的值,不可以用 *
,不然会报错:
1 | Response to preflight request doesn't pass access control check: A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. |
更新后的服务端代码:
1 | # TestController.php |
至此一般的 GET
跨域携带cookie的请求可以正常完成,那么 POST
、PUT
、DELETE
这些请求呢?
Preflighted Requests(预检请求)
Preflighted Requests是CORS中一种透明服务器验证机制。预检请求首先需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,其目的就是为了判断实际发送的请求是否是安全的。
什么是OPTIONS请求
RFC2616标准(现行的HTTP/1.1)中定义了 options
请求,主要用途有两个:
- 获取服务器支持的HTTP请求方法
- 用来检查服务器的性能
什么情况会触发预检请求呢?就是上面提到的POST
、PUT
、DELETE
等请求,大概来讲就是
RFC2616标准中规定的一些非 Safe Methods
。
回过头看,现在js代码加上发起 POST
的ajax请求,打开控制台发现只有一条 OPTIONS
请求,并无 POST
,而且报了跟最开始 GET
请求一样的跨域错误,什么情况?
Laravel路由逻辑
1 | # routes/api.php |
路由文件中并未定义‘/test’的 OPTIONS
类型请求,laravel是怎么匹配且响应200的?分析也可发现这个 OPTIONS
请求没有进到此api路由文件的生命周期内,因为没有加上允许跨域的头部,问题在哪儿?
1 | # vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php |
定位到Laravel路由的逻辑后知道了其中的端倪:
首先根据当前HTTP方法(GET/POST/PUT/…)查找是否有匹配的路由,如果有
(if(! is_null($route))
条件成立),非常好,绑定后直接返回,继续此后的调用流程即可;否则,根据$request的路由找到可能匹配的HTTP方法(即URL匹配,但是HTTP请求方式为其它品种的),如果
count($others) > 0)
条件成立,则继续进入$this->getRouteForMethods($request, $others)
方法;否则抛出NotFoundHttpException,即上述说到的
404 NOT FOUND
错误。
倘若走的是第2步,可看到函数逻辑为:
1 | # vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php |
判断如果请求方式是OPTIONS
,则返回状态码为200的正确响应(但是没有添加任何header信息),否则返回一个methodNotAllowed
状态码为405的错误(即请求方式不允许的情况)。
由此可见,Laravel针对OPTIONS
方式的HTTP请求处理方式已经固定了,最笨的方法是对跨域请求的每一个GET或POST请求都撰写一个同名的OPTIONS
类型的路由,添加允许跨域的header,也有其他方法进行处理。
Laravel options请求跨域处理
中间件方案
在文件 app/Http/Kernel.php
中,有两处可以定义中间件。
1 | # app/Http/Kernel.php |
第一处是总中间件 $middleware
,任何请求都会通过这里;
第二处是群组中间件 middlewareGroups
,只有路由匹配上对应群组模式的才会通过这部分,之前的OPTIONS请求尚未通过此处中间件的handle函数,就会返回。
因此我们添加的中间件,需要添加到$middleware数组中,不能添加到api群组路由中间件中。
在app/Http/Middleware
文件夹下新建PreflightResponse.php
文件:
1 | #PreflightResponse.php |
其中这里针对OPTIONS请求的处理内容是添加多个header内容,可根据实际需要修改相关处理逻辑。
通配路由匹配方案
1 | Route::options('/{all}', function(Request $request) { |
这样所有的OPTIONS请求都能找到匹配的路由,在此处可统一处理所有OPTIONS请求,不需要额外进行处理。
参考
https://www.cnblogs.com/virtual/p/3720750.html
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
https://zhuanlan.zhihu.com/p/33542992
https://www.jianshu.com/p/552daaf2869c