美文网首页web
跨域问题的产生及解决

跨域问题的产生及解决

作者: 嘭嘭嘭鹏 | 来源:发表于2019-03-20 09:31 被阅读0次

    跨域在接口调用的时候经常会出现,它是基于什么原因产生的呢?

    说到跨域就必须提到同源策略。什么是同源策略呢?

    同源策略是由 Netscape 公司提出的一个著名的安全策略,所有支持 JavaScript 的浏览器都会使用这个策略。它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指,域名,协议,端口相同。当页面在执行一个脚本时会检查访问的资源是否同源,如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

    同源策略一般又分为以下两种:

    DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

    什么是跨域呢?

    跨域,指的是从一个域名去请求另外一个域名的资源。即跨域名请求!跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。
    跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。


    跨域报错

    为什么要跨域?

    现实工作开发中经常会有跨域的情况,因为公司会有很多项目,也会有很多子域名,各个项目或者网站之间需要相互调用对方的资源,避免不了跨域请求。

    介绍几种跨域解决方案

    1.通过jsonp跨域
    • jsonp是什么呢?
      jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
    • jsonp的产生
      AJAX直接请求普通文件存在跨域无权限访问的问题,不管是静态页面也好,不过我们在调用js文件的时候又不受跨域影响,比如引入jquery框架的,或者是调用相片的时候,凡是拥有scr这个属性的标签都可以跨域例如<script><img><iframe>,如果想通过纯web端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进js格式的文件里,而json又是一个轻量级的数据格式,还被js原生支持,为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback 参数给服务端,
      (1).使用jquery的getJSON()方法,需要注意的是,url中要添加一个参数:callback=?
    var id_number = $("#idNumber").val();
    var user_name = $("#staffName").val();
    var url = "http://132.228.156.103:9188/DataSync/CheckResult?callback=?&SeqNo=1&ChannelID=1003" +
    "&ID="+id_number+"&Name="+user_name;    
    $.getJSON(url,function(data){
      if(data.result == "00"){
        console.log(data.smsg);
      }
    });
    

    (2).jsonp形式的ajax请求:并且通过get请求的方式传入参数。
    注意:跨域请求是只能是get请求不能使用post请求

    var url = "http://132.228.156.103:9188/DataSync/CheckResult?callback=?&SeqNo=1&ChannelID=1003" +
    "&ID="+id_number+"&Name="+user_name;
    $.ajax({
      type:'GET',
      url : url,
      jsonpCallback: 'jsonCallback',
      contentType: "application/json",
      dataType:"jsonp",
      success:function(json){
        alert(json);
      }
    });
    
    • jsonp 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
      jsonpCallback 自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
    2.document.domain + iframe跨域

    此方案仅限主域相同,子域不同的跨域应用场景。
    实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
    a.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
            <script type="text/javascript">
                document.domain = 'study.cn';
                function test() {
                    alert(document.getElementById('a').contentWindow);
                }
            </script>
    </head>
    <body>
        <iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
    </body>
    </html>
    

    b.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    
    <script type="text/javascript">
    document.domain = 'study.cn';
    </script>
    </head>
    <body>
        我是b.study.cn的body
    </body>
    </html>
    

    我们就可以通过js访问到iframe中的各种属性和对象了

    如果你想在http://a.study.cn/a.html页面中通过ajax直接请求页面http://b.study.cn/b.html,即使你设置了相同的document.domain也还是不行的.

    所以修改document.domain的方法只适用于不同子域的框架(父类与子类)间的交互。

    如果想通过使用ajax的方法去与不同子域间的数据交互或者是js调用,只有两种方法,一种是使用jsonp的方法外,还有一种是使用iframe来做一个代理。

    原理就是让这个 iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据 的,

    然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发 送ajax请求,然后收到的数据我们也可以获得了。

    3.location.hash + iframe

    实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

    具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
    a.html

    <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
    <script>
        var iframe = document.getElementById('iframe');
    
        // 向b.html传hash值
        setTimeout(function() {
            iframe.src = iframe.src + '#user=admin';
        }, 1000);
        
        // 开放给同域c.html的回调方法
        function onCallback(res) {
            alert('data from c.html ---> ' + res);
        }
    </script>
    

    b.html

    <iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
    <script>
        var iframe = document.getElementById('iframe');
    
        // 监听a.html传来的hash值,再传给c.html
        window.onhashchange = function () {
            iframe.src = iframe.src + location.hash;
        };
    </script>
    

    c.html

    <script>
        // 监听b.html传来的hash值
        window.onhashchange = function () {
            // 再通过操作同域a.html的js回调,将结果传回
            window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
        };
    </script>
    
    4.window.name + iframe跨域

    window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
    a.html

    var proxy = function(url, callback) {
        var state = 0;
        var iframe = document.createElement('iframe');
    
        // 加载跨域页面
        iframe.src = url;
    
        // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
        iframe.onload = function() {
            if (state === 1) {
                // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
                callback(iframe.contentWindow.name);
                destoryFrame();
    
            } else if (state === 0) {
                // 第1次onload(跨域页)成功后,切换到同域代理页面
                iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
                state = 1;
            }
        };
    
        document.body.appendChild(iframe);
    
        // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
        function destoryFrame() {
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.body.removeChild(iframe);
        }
    };
    
    // 请求跨域b页面数据
    proxy('http://www.domain2.com/b.html', function(data){
        alert(data);
    });
    

    proxy.html,中间代理页,与a.html同域,内容为空即可。

    b.html

    <script>
        window.name = 'This is domain2 data!';
    </script>
    

    总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

    5.postMessage跨域

    postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
    a.) 页面和其打开的新窗口的数据传递
    b.) 多窗口之间消息传递
    c.) 页面与嵌套的iframe消息传递
    d.) 上面三个场景的跨域数据传递

    用法:postMessage(data,origin)方法接受两个参数
    data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
    origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
    a.html

    <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
    <script>       
        var iframe = document.getElementById('iframe');
        iframe.onload = function() {
            var data = {
                name: 'aym'
            };
            // 向domain2传送跨域数据
            iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
        };
    
        // 接受domain2返回数据
        window.addEventListener('message', function(e) {
            alert('data from domain2 ---> ' + e.data);
        }, false);
    </script>
    

    b.html

    <script>
        // 接收domain1的数据
        window.addEventListener('message', function(e) {
            alert('data from domain1 ---> ' + e.data);
    
            var data = JSON.parse(e.data);
            if (data) {
                data.number = 16;
    
                // 处理后再发回domain1
                window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
            }
        }, false);
    </script>
    
    6.跨域资源共享(CORS)

    普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

    需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:七、nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。

    目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。

    7.nginx代理跨域
    8.nodejs中间件代理跨域
    9.WebSocket协议跨域

    参考文章:https://segmentfault.com/a/1190000011145364

    相关文章

      网友评论

        本文标题:跨域问题的产生及解决

        本文链接:https://www.haomeiwen.com/subject/tjeamqtx.html