九种跨域解决方案

作者: CRUD_科科 | 来源:发表于2019-03-18 10:47 被阅读4次
    何为跨域

      想必大家都知道,不在赘述了,跨域问题出现的原因就是浏览器的安全机制:同协议、域名、端口。下面就总结下常用的几种解决方案。

    1、JSONP(只能发送get请求,不支持post、put、delete;不安全xss攻击)

      jsonp的详细介绍在另一篇文章,这里就以百度的查询接口做简单展示:

    <script>
    // 封装简单的jsonp
    function jsonp(url, params, cb) {
      return new Promise((resovle, reject) => {
        let script = document.creatElement('script');
        window[cb] = function(data) {
          resovle(data);
          document.body.removeChild(script);
        }
        params = {...params, cb}; //wd=b&cb=show
        let arrs = [];
        for(let key in params) {
          arrs.push(`${key}=${params[key]}`);
        }
        script.url =  `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
     })
    }
    // jsonp调用方式
    jsonp({
      url:'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
      params:{wd: 'b'},
      cb: show
    }).then(data=>{
      console.log(data)
    })
    </script>
    
    2、cors(后台配置)

      下面以express为例

    // 设置那些原可以访问接口
    res.setHeader('Access-Control-Allow-Origin', origin) 
    // 允许携带哪个头访问
    res.setHeader('Access-Control-Allow-Headers', 'name') 
    // 允许那些请求方法
    res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,DELETE') 
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials',true) 
    // 预检测存活时间(options请求)
    res.setHeader('Access-Control-Max-Age',6000) 
    // 允许前端获取哪个请求头(允许返回的头)
    res.setHeader('Access-Control-Expose-Header','name') 
    
    3、iframe postMessage

      postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题:

    • 页面和其打开的新窗口的数据传递
    • 多窗口之间消息传递
    • 页面与嵌套的 iframe 消息传递
    • 上面三个场景的跨域数据传递
      postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

    otherWindow.postMessage(message, targetOrigin, [transfer]);

    • message: 将要发送到其他 window 的数据。
    • targetOrigin:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
    • transfer(可选):是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
      接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回"我不爱你"。
    // a.html
      <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe//等它加载完触发一个事件
      //内嵌在http://localhost:3000/a.html
        <script>
          function load() {
            let frame = document.getElementById('frame')
            frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
            window.onmessage = function(e) { //接受返回数据
              console.log(e.data) //我不爱你
            }
          }
        </script>
    
    // b.html
      window.onmessage = function(e) {
        console.log(e.data) //我爱你
        e.source.postMessage('我不爱你', e.origin)
     }
    
    4、window.name

    window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
      a、b页面同域,a页面嵌套c页面,onload事件第一次载入c页面url变为b页面并且获取contentWindow.name

     // a.html(http://localhost:3000/b.html)
      <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
      <script>
        let first = true
        // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
        function load() {
          if(first){
          // 第1次onload(跨域页)成功后,切换到同域代理页面
            let iframe = document.getElementById('iframe');
            iframe.src = 'http://localhost:3000/b.html';
            first = false;
          }else{
          // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
            console.log(iframe.contentWindow.name);
          }
        }
      </script>
    
    // c.html(http://localhost:4000/c.html)
      <script>
        window.name = '我不爱你'
      </script>
    
    5、hash

      a中嵌套c,c中嵌套b,a=>b=>c传递location.hash,a页面用window.onhashchange获取hash值

    // a.html
      <iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
      <script>
        window.onhashchange = function () { //检测hash的变化
          console.log(location.hash);
        }
      </script>
     // b.html
      <script>
        window.parent.parent.location.hash = location.hash
        //b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
      </script>
     // c.html
     console.log(location.hash);
      let iframe = document.createElement('iframe');
      iframe.src = 'http://localhost:3000/b.html#idontloveyou';
      document.body.appendChild(iframe);
    
    6、document.domain

      必须是一级域名和二级域名的关系

    // a.html
    <body>
     helloa
      <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
      <script>
        document.domain = 'zf1.cn'
        function load() {
          console.log(frame.contentWindow.a);
        }
      </script>
    </body>
    // b.html
    <body>
       hellob
       <script>
         document.domain = 'zf1.cn'
         var a = 100;
       </script>
    </body>
    
    7、websocket

    可以参考阮大的文章

    // socket.html
    <script>
        let socket = new WebSocket('ws://localhost:3000');
        socket.onopen = function () {
          socket.send('我爱你');//向服务器发送数据
        }
        socket.onmessage = function (e) {
          console.log(e.data);//接收服务器返回的数据
        }
    </script>
    // server.js
    let express = require('express');
    let app = express();
    let WebSocket = require('ws');//记得安装ws
    let wss = new WebSocket.Server({port:3000});
    wss.on('connection',function(ws) {
      ws.on('message', function (data) {
        console.log(data);
        ws.send('我不爱你')
      });
    })
    
    8、ngxin

    实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。

    使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。

    实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。

    先下载nginx,然后将 nginx 目录下的 nginx.conf 修改如下:

    // proxy服务器
    server {
        listen       80;
        server_name  www.domain1.com;
        location / {
            proxy_pass   http://www.domain2.com:8080;  #反向代理
            proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
            index  index.html index.htm;
    
            # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
            add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
            add_header Access-Control-Allow-Credentials true;
        }
    }
    
    9、webpack proxy
    //服务器启动目录;
      devServer: {
        contentBase: './dist',
        hot: true,
        // host:'1ocalhost',
        port: 8586,
        // compress:true,
    
        //解决跨域
        proxy: {
          '/api': {
            target: 'http://localhost:8087',
            pathRewrite: { '^/api': '' },
            changeOrigin: true,
            secure: false, // 接受 运行在 https 上的服务
          }
        }
      },
    

    相关文章

      网友评论

        本文标题:九种跨域解决方案

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