学习跨域问题及解决Laravel的跨域问题

 Hygge   2021-05-25 08:42   54 人阅读  2 条评论

在开发前后端分离的程序时,我这边后台只负责写接口

前端调用的接口时候就会产生跨域问题

一、什么是跨域问题

跨域问题: 前端和后端的域名、协议、端口 有一个不同就不是同

源,三者均相同,这两个网站才是同源。

注1:同源策略只针对浏览器端,浏览器一旦检测到请求的结果的域名不一致后,会阻塞请求结果。这里注意,跨域请求是可以发过去的,但是请求响应response被浏览器堵塞了。

注2:同源策略的非绝对性:<script><img><iframe><link><video><audio>等等带有src属性的标签可以从不同的域加载和执行资源(即不受同源策略影响)

二、跨域问题的解决方法

Jsonp

JSONP 是服务器与客户端跨源通信的常用方法。

最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。

CORS

CORS(Cross-Origin Resource Sharing)跨域资源共享,是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源(允许跨域访问)

注:整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

CORS的请求

CORS分为两种请求,一种是简单请求,另一种是非简单请求。

为什么要分为简单请求和非简单请求?

因为浏览器对这两种请求方式的处理方式是不同的。

1. 简单请求

  • 请求方式为HEADPOST 或者 GET

  • http头信息不超出以下字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(限于三个值:application/x-www-form-urlencodedmultipart/form-datatext/plain) 只要满足以上两个条件的请求就是简单请求;

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

origin:"http://localhost:8080"

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

浏览器会根据此次请求的响应头response中是否包含Access-Control-Allow-Origin来判断跨域请求是否成功;包含则成功;否则反之;  Access-Control-Allow-Origin的值代表着允许访问该资源的"源";

此外,跨域成功的响应头中可能会包含其他字段: 如:  Access-Control-Allow-Credentials :布尔值, 表明服务器是否允许发送Cookie;默认情况下,Cookie不包括在CORS请求之中;该字段设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。

Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

CORS请求默认不发送Cookie和HTTP认证信息。

如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。 另一方面,开发者必须在Ajax、Axios请求中打开withCredentials属性。

axios.defaults.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

注:如果要发送CookieAccess-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。


2.非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

预检请求

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。

浏览器发现请求是一个非简单请求时,就自动发出一个"预检"请求,要求服务器确认是否可以这样请求。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。 除了Origin字段,"预检"请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。

  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

预检请求的响应

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-MethodAccess-Control-Request-Headers字段以后,会有两种结果:

  • 如果确认允许跨源请求,就可以做出一个包含Access-Control-Allow-Origin和其他字段的响应;

  • 如果不允许跨源请求,会做出一个没有任何CORS相关的头信息字段的响应;

服务器回应的CORS相关字段如下:

'Access-Control-Allow-Origin: '
// 跨域成功请求的响应头必须字段;
// 值为指定源 或者 *;*是指所有源
'Access-Control-Request-Method:'
// 返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
// 值为POST,GET......等
// 注:当请求是"预检"请求时,该字段是必须的
'Access-Control-Request-Headers:' 
// 如果浏览器请求包括Access-Control-Request-Headers字段,
// 则Access-Control-Allow-Headers字段是必需的。
// 它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
'Access-Control-Allow-Credentials:'
//  该字段与简单请求时的含义相同。
// 布尔值, 表明服务器是否允许发送Cookie;
// 默认情况下,Cookie不包括在CORS请求之中;
// 该字段设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
'Access-Control-Max-Age:1728000'
// 该字段可选,用来指定本次预检请求的有效期,单位为秒。
// 有效期是1728000秒,即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

使用服务器代理

服务器代理的原理大概是这样:

代理服务器和访问源(请求端)是同源的,但和被访问服务器(资源端)是不同源的,但服务器之间的访问不受浏览器同源策略的影响(即不必担心是否有跨域问题),那么我们即可请求到同源服务器上的从被访问服务器上的获取到的请求资源了

例:例如www.123.com/index.html需要调用www.456.com/server.php,可以写一个接口www.123.com/server.php,由这个接口在后端去调用www.456.com/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。

目前我所知道的两种服务器代理方法:

1. Nginx反向代理

2. Node服务器

在我所参与的Vue项目中,解决跨域问题的方式就是使用代理的方式;  开发环境下使用Node做代理;通过Webpack的配置即可实现;

生产环境下使用Nginx做代理;重写Nginx配置文件即可实现;

以上内容 参考文章:浏览器跨域问题:https://www.jianshu.com/p/ef0b985d3054

三、Laravel跨域问题的解决

通过上文可以知道,跨域问题想在客户端解决 就必须使用jsonp,但是jsonp只支持get所以不考虑。

那么就准备从laravel服务端解决,服务器端解决就是要对返回的响应体进行修改。

这里用到了laravel中的中间件(middleware):可以为路由绑定中间件,这样在每次访问这个路由时都会执行某些固定的代码

  1. 定义中间件

这里使用了php artisan创建控制器

# php artisan make:middleware 中间件名字
php artisan make:middleware AccessControllerAllowOrigin
  1. 编写中间件

<?php

namespace App\Http\Middleware;

use Closure;

class AccessControlAllowOrigin
{
   public function handle($request, Closure $next)
   {
       $response = $next($request);
       $origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
       $allow_origin = [
           'http://127.0.0.1:8080',//允许访问
       ];
       // 如果需要传递Cookie等信息,则将ip填入到$allow_origin
       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');
       }else {
           $response->header('Access-Control-Allow-Origin','*');
           $response->header('Access-Control-Allow-Headers','Content-Type');
           $response->header('Access-Control-Allow-Methods','GET,POST');
       }
       return $response;
   }
}

上面的在我开发中已经够用了,实际场景可以根据这个修改。

  1. 全局所有请求应用中间件

先打开Kernel.php

/**
    * The application's global HTTP middleware stack.
    *
    * These middleware are run during every request to your application.
    *
    * @var array
    */
   protected $middleware = [
       \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,
       // 追加一行 新定义的中间件即可。
       \App\Http\Middleware\AccessControlAllowOrigin::class // 新增跨域中间件
   ];


网关解决Response to preflight request doesn‘t pass access control check: It does not have HTTP ok status

20200819112912844.png

前后台联调接口遇到一下问题,乍一看是跨域问题,但其实并不是跨域问题。由于浏览器将CORS请求分为两类:简单请求(simple request)和非简单请求(not-simple-request)。非简单请求 会在正式通信之前,增加一次HTTP请求,称之为预检请求。浏览器会先发起OPTIONS方法到服务器,以获知服务器是否允许该实际请求。

这是在第一次预请求时候没有请求成功,预请求不成功。

第一,先找到后台对应的接口,让后台去检查接口是否有抛出异常但是没有正常捕获。

第二,前端vue中注意点是请求的时候有没有使用content-type:applicationjson还有qs.string()

———————————————— 该小节出处:小达哥的垃圾桶


我是因为路由定义在api.php中,然后域名没有添加/api,调试了半天 结果最后手动去访问是404...粗心了


本文地址:https://blog.lisok.cn/post/45.html
版权声明:本文为原创文章,版权归 Hygge 所有,欢迎分享本文,转载请保留出处!
 相关文章  关键词:

 发表评论


表情

 评论列表

  1. 神秘的访客
    神秘的访客  @回复

    笑死我了哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

  2. 神秘的访客
    神秘的访客  @回复

    写的真不错