跨域解决方案

150 阅读3分钟

跨域解决方案

同源策略

  • 端口,协议,域名只要有一个不同,就不是同源

跨域的应用

1.JSNONP

  • 思路:通过script标签引入,在参数中拼上回调函数,响应中返回定义的函数执行
  • jsonp帮我们创建一个函数,并且创建立一个script
  • script,img,iframe不受同源策略的影响
  • 缺点: 只能发送get请求并且不安全,可能引起xss攻击
function Jsonp({ url, params, cb }) {
  return new Promise(function(reslove, reject) {
    let script = document.createElement('script')
    let paramsArr = []
    window[cb] = function(data) {
      reslove(data)
    }
    let params = { ...params, cb }
    for(let key in params) {
      paramsArr.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${[paramsArr.join('&')]}`
    document.body.appendChild(script)
    document.removeChild(script)
  })
}

2.cors

  • 靠服务端验证,因为安全性高,现在是主流
  • origin: 当前访问的是哪个源
服务器验证客户端传来的origin,在白名单内,服务器响应头设置'Access-Control-Allow-Origin'
Access-Control-Allow-Origin: 可以访问的源或者'*'
Access-Control-Allow-Headers: 允许携带哪个头,'a, b',或者'*'
Access-Control-Allow-Methods: 'PUT, ....'或者'*'
Access-Control-Max-Age: 6000  // 预检的存活事件
cookie
  • 设置cookie: document.cookie = 'name=xxx',cookie默认不用许跨域
  • xhr.withCredentials = true
  • 服务端设置Access-Control-Allow-Credentials:true // 允许携带cookie

3.iframe + postmessage

  • 实现两个不同源页面的通信
  • 需要两个页面
// A.html
<iframe src="./B.html" fzrazmeborder="0" id="iframe" onload="load()"></iframe>
<script>
    function load() {
      let iframe = document.getElementById('iframe')
      iframe.contentWindow.postMessage('xxx', './B.html') // ifame内部的window就是B
    }

    window.onmessage = function(e) {
      // e.data // B发过来的信息
    }

  </script>
  
// B.html
window.onmessage = function (e) {
  // e.data // A发过来的信息
  e.source.postMessage(xxx, './A.html')
}

4. iframe + document.domain

  • 用于一级域名一样,二级域名不同,两个页面
// A.html
<iframe src="./B.html" frameborder="0" onload=""load></iframe>
<script>
document.domain = 'xxx'
function load() {
  let iframe = document.getElementById('iframe')
  console.log(iframe.contentWindow.a) // 1
}

</script>

// B.html
document.domain = 'xxx'
let a = 1

5. iframe + hash

  • 路径后的hash值可以用来通信
  • A和B是同源, C是独立域的
  • A给C传一个hash。C收到hash值后,把hash值传给B, B将结果放在A的hash中
// A.html,给C设置一个hash
<iframe src="./C.html#xxx" frameborder="0"></iframe>
<script>
  window.addEventListener('hashChange', function() {
    console.log(location.hash)
  })

</script>

// B.html, 和A.html同源,在B中改变A的hash
window.parent.parent.location.hash = location.hash

// C.html,将hash传给B
<script>
    let iframe = document.createElement('iframe')
    iframe.src = './B.html#xxx'
    document.body.appendChild(iframe)
</script>

6. iframe + window.name

  • A和B是同源, C是独立域的
  • A引用C,C把值放在window的name上,把A引用地址改像B
// A.html
<iframe src="./C.html" frameborder="0" id="iframe" onload="load()"></iframe>
<script>
    let first = true
    function load() {
      let iframe = document.getElementById('iframe')
      iframe.src = './B.html'
      first = false
    } else {
        console.log(iframe.contentWindow.name) // xxx
    }
  }
</script>

// B.html和A.html同源

// C.html
<script>
    window.name = 'xxx'
</script>

7.socket-io

  • 双工通信,A发给服务端
  • 基于WebSocket,有个socket-io库
  • socket-io没有同源限制
A.html
<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>

// node.js后台
var http = require('http');
var socket = require('socket.io');

// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // client就是客户端,接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

8.nginx

server {
    listen       81;
    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.http-proxy(反向代理)

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}