美文网首页
跨域解决方法

跨域解决方法

作者: _咻咻咻咻咻 | 来源:发表于2021-01-27 10:12 被阅读0次

    一、概念

    同源:域名、端口、协议均相同。
    跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的

    二、跨域解决方法

    1. 跨域资源共享(CORS)
    2. jsonp
    3. nginx代理跨域
    4. webpack设置代理proxy/nodejs中间件代理跨域
    5. postMessage跨域
    6. WebSocket协议跨域
    7. document.domain + iframe跨域
    8. location.hash + iframe跨域
    9. window.name + iframe跨域

    以上9种常见的跨域解决方案,
    CORS(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;
    jsonp(只支持get请求,支持老的IE浏览器)适合加载不同域名的js、css,img等静态资源;
    Nginx代理跨域和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口。
    postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。
    document.domain+iframe适合主域名相同,子域名不同的跨域请求。

    1、跨域资源共享(CORS)

    所有的跨域请求(简单或非简单)总会包含一个origin的请求头部,由浏览器添加不受用户控制。值由协议、域名、端口组成,说明请求的来源。下面为一个Origin头部示例:
    Origin: http://0.0.0.0:8000
    服务器接受这个请求,会在响应头Access-Control-Allow-Origin回发相同的源信息。( * 表明该资源可以被任意外域访问)
    Access-Control-Allow-Origin:http://0.0.0.0:8000

    2、jsonp

    jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
    jsonp只能实现get一种请求。

    原生实现:(各框架和异步请求插件都有对jsonp请求的请求方式的封装,照搬即可)

      <script>
        var script = document.createElement('script');
        script.type = 'text/javascript';
    
        // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
        script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
        document.head.appendChild(script);
    
        // 回调执行函数
        function handleCallback(res) {
            alert(JSON.stringify(res));
        }
     </script>
    

    服务端返回如下(返回时即执行全局函数):

    handleCallback({"status": true, "user": "admin"})
    
    3、nginx代理跨域
    1. nginx配置解决iconfont跨域
    2. nginx反向代理接口跨域
    4、 webpack设置代理proxy/nodejs中间件代理跨域
    1. 使用webpack搭建的项目,可以在webpack配置文件配置proxy代理
     // 接口代理示例
      proxy: {
        "/boss": {
          "target": "https://test-overseas.91dbq.com",
          "changeOrigin": true, //如果跨域,需要配置这个参数
          "secure":false, //如果是https请求,要配置这个参数
          "pathRewrite": { "^/boss" : "/boss-gateway-sg/boss" }
        }
      },
    
    1. 原生可以使用中间件代理
    // 中间件服务器
    var express = require('express');
    var proxy = require('http-proxy-middleware');
    var app = express();
    
    app.use('/', proxy({
        // 代理跨域目标接口
        target: 'https://test-overseas.91dbq.com',
        changeOrigin: true,
    
        // 修改响应头信息,实现跨域并允许带cookie
        onProxyRes: function(proxyRes, req, res) {
            res.header('Access-Control-Allow-Origin', 'http://0.0.0.0:8000');
            res.header('Access-Control-Allow-Credentials', 'true');
        },
    
        // 修改响应信息中的cookie域名
        cookieDomainRewrite: '0.0.0.0:8000'  // 可以为false,表示不修改
    }));
    
    app.listen(3000);
    console.log('Proxy server is listen at port 3000...');
    
    5、postMessage跨域
    6、WebSocket协议跨域

    WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
    原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

    <div>user input:<input type="text"></div>
    <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
    <script>
    var socket = io('http://www.domain2.com:8080');
    // 连接成功处理
    socket.on('connect', function() {
        // 监听服务端消息
        socket.on('message', function(msg) {
            console.log('data from server: ---> ' + msg); 
        });
        // 监听服务端关闭
        socket.on('disconnect', function() { 
            console.log('Server socket has closed.'); 
        });
    });
    document.getElementsByTagName('input')[0].onblur = function() {
        socket.send(this.value);
    };
    </script>
    
    7、document.domain + iframe跨域

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

    <iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
    <script>
        document.domain = 'domain.com';
        var user = 'admin';
    </script>
    

    2.)子窗口: http://child.domain.com/b.html

    <script>
        document.domain = 'domain.com';
        // 获取父窗口中变量
        alert('get js data from parent ---> ' + window.parent.user);
    </script>
    
    8、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页面所有对象。

    1.)a.html:http://www.domain1.com/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>
    

    2.)b.html:http://www.domain2.com/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>
    

    3.)c.html:http://www.domain1.com/c.html

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

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

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

    2.)a.html:http://www.domain1.com/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(跨域页)成功后,此时已拿到window.name,然后切换到同域代理页面
                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); //This is domain2 data!
    });
    

    3.)proxy.html:http://www.domain1.com/proxy.html
    中间代理页,与a.html同域,内容为空即可。

    相关文章

      网友评论

          本文标题:跨域解决方法

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