跨域问题的产生及解决

跨域问题的产生及解决

开发过程中,或多或少都碰见过这样的情况,本地写了个ajax请求想从某某域下获取数据,代码如下:

      $.ajax({
          url:"https://www.baidu.com",
          data:{
              name:"name",
              address:"beijing"
          },
          type:"post",
          success:function(res){
    
          },
          error:function(err){
    
          }
      });

运行之后出现如下图中的报错。
在这里插入图片描述
浏览器提示:到http://www.baidu.com的请求被CORS策略阻止,资源的请求头上没有“Access-Control-Allow-Origin”标头,不允许访问。

简单来说就是出现跨域问题了!!!请求被阻止了 说到跨域,首先什么是跨域?

问题: 浏览器提示出现跨域了,,,,那这个请求服务器端是否能收到呢?

这个请求仍然会被发送到服务器,也就是服务器会收到这个请求。浏览器会拦截跨域请求只是拦截返回结果。

首先,请求被拦截之后会被改成option请求送达服务器。这样服务器知道有人在请求。

已经跨域了,服务器依然能收到请求,这样是有意义的,让服务器决定要不要运行是更为合理更为科学的。

一、什么是跨域?

跨域,是指一个域下的文档或脚本去请求另一个域下的的资源。

二、出现跨域问题为什么报错?

这是由于浏览器同源策略的限制。

所谓同源是指:协议、域名、端口三者都要相同,即使两个不同的域名指向同一个IP地址,也是非同源。 一句话总结:只要请求的资源URL的协议、域名、端口号三者任意一个与当前页面的不同就会出现跨域问题。

同源策略是一种约定,用于保障浏览器最基本最核心的安全问题,若是缺少同源策略浏览器的正常功能可能都会受到影响。试想一下,如果没有同源(同协议、同域名、同端口)策略,不同源的数据和资源(Cookie、localStorage等)就能随意访问,这种情况简直不敢想象。所以为了浏览器采用同源策略是非常必要的。

但是,但是,问题又来了,在本地请求react.development.js时

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>

按照前面的说法,本地和react.development.js并不同源(本地域名为dev1.adc.com,react.development.js文件所在域域名为unpkg.com),也是跨域请求,为什么确可以正常请求呢?

原因就在于,同源策略只限制特定的跨域请求,对于像css/js/image这些静态文件的跨域请求并不干涉。

三、 同源策略限制的几种行为:

  1. Cookie、localStorage和indexDB读取
  2. DOM 和js对象获取
  3. ajax请求

在工作中,很容易遇见需要跨域请求数据的情况,如本地为http://localhost:9990,需要请求的接口地址在http://dev4.byy.com。按照前面的描述同源策略会限制这次请求。那该如何解决呢?

四、解决跨域问题的几种方法

本文只列出前端处理部分

1、jsonp

我们知道js、image、css之类的静态文件是不存在跨域问题的。而jsonp的原理正是这个。

首先前后端需要沟通确定好回调函数,并将其作为URL的参数。 后端接收到请求后,通过该参数获取到回调函数名称,并将处理结果作为回调函数的参数将其返回
。 收到结果后,因为是script标签,浏览器会当做是脚本继续执行,从而达到跨域获取数据的目的。

1.1 jsonp前端实现
     <script>
            var script=document.createElement("script");
            script.src="http://dev4.byy.com?callback=jsonpCallback";
            // 执行回调函数
            document.head.appendChild(script);
            function jsonpCallback(data){
                   .....
            }
        </script>
1.2 接收到的返回内容
  jsonpCallback({
       "name":"nanjing",
       "address":"jiangsu"
    });
1.3 jQuery方法实现jsonp
  $.ajax({
      dataType:"jsonp", //  必须写
      url:"aaa/bbb/ccc",
      success:function(result){
         console.log(result); 
      },
      error:function(error){
         console.log(error);
      }    
   })

   // 直接使用jsonp()
   $.jsonp({
       url:"aaa/bbb/ccc",
       data:{
           name:"clicke"
       },
       success:function(res){},
       error:function(error){}  
   })
1.4 jsonp解决跨域的优点
  1. 兼容性很好,低版本浏览器也能无压力的执行
  2. 不需要XMLHttpRequest或ActiveX的支持
1.5 jsonp解决跨域的缺点
  1. 只支持get请求(静态资源的请求方式都是get),请求的参数会追加在请求地址上,所以这种方式发送的数据是有限制的。
  2. 动态拆入script标签本身就是一种脚本注入,存在安全隐患。

2、CORS

CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了ajax只能同源使用的限制。

CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。so,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。

CORS的原理:增加额外的的HTTP头部信息让浏览器和服务器进行沟通,从而决定接受还是拒绝该请求。 因此,想要通过CORS实现跨域,服务器端和前端需要协同处理。
服务器接收到请求之后,会根据自己的跨域规则进行验证 ,验证成功后直接返回请求结果 。

从前端角度看,将CORS请求分成简单请求和非简单请求两类 。

2.1 简单请求

当请求满足下面两点时,该请求可以视为简单请求,否则为非简单请求

  1. 请求方式为get、post、head三者之一
  2. HTTP请求头不超出以下几种字段:Accept/Accept-Encoding/Accept-Language/Cache-Control/Connection/Cookie/Host/If-Modified-Since/Referer/User-Agent/Content-Type/Content-Language。其中Content-Type仅限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/pain。
2.1.1 前端实现

ajax中添加header字段,设置请求头
使用beforeSend方法设置请求头

      $.ajax({
             url:"http://dev4.byy.com/user/menusAndFunction",
             xhrFields:{withCredentials:true},
              beforeSend: function (XMLHttpRequest) {                
                     var sessionid = "SessionID";
                     XMLHttpRequest.setRequestHeader(sessionid, sid);
                },
              success:function(res){
                    console.log("成功了",res);
                },
                error:function(err){
                    console.log("失败了",err);
                }
        });
2.1.2 请求结果

在这里插入图片描述
可以看到,简单请求和普通的ajax请求并无区别,前端无需做任何设置。 代码中的xhrFields:{withCredentials:true} 是因为接口 http://dev4.byy.com/user/menusAndFunction 的请求需要带携带cookie。

请求详情图(CORS与普通ajax请求对比)
在这里插入图片描述

在这里插入图片描述

2.2 非简单请求

非简单的CORS请求会增加一次HTTP查询请求,称之为预先检验请求preflight,用于查询要被跨域访问的服务器,是否允许当前域是否可以发送跨域请求。如果预先验证请求通过,浏览器才会发送真正的跨域请求。

为什么要预

2.2.1 一次非简单请求
   $.ajax({
    url:"http://dev4.byy.com/user/menusAndFunction",
    type:"delete",  // 请求方式非get/post/head,为非简单CORS请求
    xhrFields:{withCredentials:true},
    success:function(res){
        console.log("成功了",res);
    },
    error:function(err){
        console.log("失败了",err);
    }
});
2.2.2 请求结果

在这里插入图片描述

这次非简单请求没能通过预检查请求

2.2.3 关于withCredentials 属性

CORS请求默认不发送Cookie ,如果要发送cookie到服务器。首先要在服务器端设置 Allow-Control-Allow-Credentials:true ,接着在ajax请求中设置withCredentials:true

2.3 CORS的优点
  1. 支持所有类型的HTTP请求
  2. 相较于jsonp有完善的错误处理机制,方便排查错误
2.4 CORS的缺点
  1. 浏览器的兼容性不太佳,IE浏览器需要IE10以上才支持
2.5 CORS 跨域一句话总结

洋洋洒洒写了这么毒,主要是讲一下什么是CORS,CROS如何分类。从前端来看,CORS和普通的ajax在写法上没有区别。不同的地方在于HTTP请求头和返回头多了某些特定字段(orign、allow-control-allow-origin、allow-control-allow-methods等) CORS主要是在服务器端实现。

3、document.domain

适用情况:一级域名相同而子域名不同

原理:在父页面和子页面中,通过js强制设置document.domain的值为一级域名实现同域,通过页面嵌套的方式实现页面间的相互操作。 document.domain只能从子域往高级的域名设置,往下设置或者往其他域名设置都是不允许的

假设现有两个页面 b.com域下的b.html a.b.com下的a.html 现在要实现两个页面间的通信

3.1 前端实现
//  b.com/b.html

     <iframe id="ifr" src="http://a.b.com/index.html"></iframe>
        <script>
            document.domain = 'b.com'; 
            function aa(str) {
                console.log(str);
            }
            window.onload = function () {
                document.getElementById('#ifr').contentWindow.bb('aaa');
            }
        </script>


// a.b.com/a.html
 <script>
    document.domain="b.com";
    function bb(str){
        console.log(str);
    }
    parent.aa("bbb");
  </script>

通过设置document.domain=“b.com” 把两个页面的document.domain都指向一级域名,之后这两个页面就可以像同一域下的两个页面互相访问了。

父页面中:通过ifr.contentWindow 或 ifr.contentDocument.window访问子页面 子页面中:通过window.parent或者parent访问父页面

4、window.postMessage

原理:HTML5允许窗口之间发送消息

适用条件:浏览器支持HTML5

4.1前端实现
dev4.byy.com/a.html
// localhost:9990/html/b.html
    <script> 
        window.onload=function(){
           document.getElementsByTagName("iframe")[0].contentWindow.postMessage("hello helllo","http://dev4.byy.com/a.html")
       }   
    </script>
    <iframe src="http://dev4.byy.com/a.html"></iframe>

// http://dev4.byy.com/a.html
<script>
   window.addEventListener("message",function(){
      console.log(event);
   }
</script>

在这里插入图片描述

上图中:data:传输的数据 、origin:发送消息的源 。 必须等目标窗口完全加载完毕,发送的数据才能被监听到。

4.2 安全性

使用postMessage跨域交换数据是安全的,postMessage采用的是“双向安全机制”。发送方发送数据的时候回确认接收方的origin,而接收方监听到message事件后,也用event.origin判断是否来自于正确可靠的发送方。

从localhost:9990/html/b.html 给http://www.baidu.com发送消息
在这里插入图片描述在这里插入图片描述

错误提示:不匹配,发送消息失败

5、navigation对象

原理:IE6/7上的一个漏洞,iframe共享navigation对象

适用条件:IE6、IE7

实现 – – a.com/index.html 与b.com/index.html 传递消息

//a.com/index.html
navigation.onData(){}
typeof navigation.getData()==="function"||navigation.getData()

// b.com/index.html
navigation.getData = function(){
    $.get('/path/under/b.com')
        .success(function(data){
            typeof navigation.onData === 'function'
                || navigation.onData(data)
        });
}

注:电脑上未安装IE6/7,未验证。只是提供一种解决方法。

6、nginx代理

原理:配置一个代理路径替换实际的访问路径,使得浏览器认为访问的资源都是属于相同协议、同域名、同端口的。可以认为nginx代理是给浏览器的一种障眼法

7、window.name

原理:window.name属性有一个特点,每個窗口都有獨立的window.name與之對應; 在一個窗口被關閉之前,窗口載入的所有頁面同時共享一個window.name,每個頁面都有對window.name的讀寫權限;window.name一直存在於當前窗口,即使有新的頁面載入也不會被改變;window.name可以存儲不草果2M的數據,數據格式按需自定義

代碼實現 – – localhost:9990/html/a.html 獲取 http://dev2.byy.com/html/b.html 的數據

// localhost:9990/html/a.html
<script>
   function load () {
       var iframe = document.getElementById('iframe');
       iframe.onload = function () {
          var window = iframe .contentWindow;
          console.log(window.name);
       }
       iframe.src = 'proxy.html'; //在iframe页面加载完毕的时候,再让iframe与当前页面属于同一个域下,就可以拿到window.name
   }
 </script>
 <iframe id="iframe" src="http://dev2.byy.com/html/b.html" onload="load()" style="display:none"></iframe>

執行結果
在这里插入图片描述

http://dev2.byy.com/html/b.html中設置window.name 成功獲取到http://dev2.byy.com/html/b.html中設置的window.name

8、chrome 设置 --disable-web-security 解决跨域

对于Chrome浏览器,命令行执行以下命令关闭安全限制,解决跨域

sudo  open -a "/Applications/Google Chrome.app" --args --disable-web-security  --user-data-dir=/Users/yourname/chromeDevUserData/

执行后会自打开浏览器,同时提示浏览器安全降低了。说明设置成功了。

这是一个临时的方法,并不明真正解决问题,每次退出浏览器再次打卡都需要执行上面的动作。

五、总结

实现跨域的方法非常多,比如localtion.hash 、web Sockets、flash URLLoader等等等等,本文档并没有列出全部。
由于本身条件限制,文档中提到的方法6未能实地验证。
这么多实现方法中个人倾向于JSONP+CORS 两种结合使用, 其中JSONP用于兼容较低版本的浏览。相较于文中列出的其他方法,前端不需要做任何配置,不需要额外使用标签iframe,代码上更加清晰简洁。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值