首页 > 其他 > 详细

跨域及其解决方案

时间:2020-06-06 09:21:23      阅读:44      评论:0      收藏:0      [点我收藏+]

同源策略请求

  • 同源策略是 浏览器的安全策略 ,不是HTTP协议的一部分。所以 服务器端 调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
  • 同源是指 协议+域名+端口号 三者相同,只要有一个不一样,就会发生跨域
  • ajax / fetch 做请求的时候去调用接口url,可能就会产生跨域

1、JSONP

  • scriptimglinkiframe 标签不存在跨域请求限制
  • 通过 script标签 去调用跨域的接口,把 参数回调函数名(需要是全局函数)传给服务器,服务器接收客户端请求,给客户端返回 回调函数数据的字符串,浏览器拿到字符串进行解析,然后执行函数。
  • 缺点:只支持 GET请求、需要服务端支持、安全性不好
  • 样例:
    后端接口:
// 处理成功失败返回格式的工具
const {successBody} = require(‘../utli‘)
class CrossDomain {
  static async jsonp (ctx) {
    // 前端传过来的参数
    const query = ctx.request.query
    // 设置一个cookies
    ctx.cookies.set(‘tokenId‘, ‘1‘)
    // query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端
    // 由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
    ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, ‘success‘))})`
  }		
// 因为返回的数据是字符串,所以这里要写stringify使浏览器解析完后能执行把数据转成json格式
}
module.exports = CrossDomain

简单版前端:

// 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
window.jsonpCb = function (res) { console.log(res) }
<script src=‘http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb‘></script>

2、CORS跨域资源共享

  • 普通跨域请求:只服务端设置 ~Access-Control-Allow-Origin` 即可,前端无须设置
  • 若要带 cookie 请求:前后端都需要设置。xhr.withCredentials = true; // 前端设置是否带cookie
  • 所有的跨域请求会有 两个请求,浏览器会预先发送 OPTIONS(试探性)请求,看是否可以和服务器建立跨域传输,再传真正的请求。

3、Nodejs中间件Proxy代理跨域

  • node中间件 实现跨域代理,原理大致与nginx相同,都是通过一个 代理服务器,实现 数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
  • 配置 webpackwebpack-dev-server
  • 用node模拟了一个nginx服务请求,处理跨域请求
    技术分享图片

4、nginx反向代理

这个暂时还未去学习,先空着吧,大概就是有这么个东西

5、postMessage

  • postMessage 是HTML5 XMLHttpRequest Level 2中的API,且是为数不多 可以跨域操作的windowd属性* 之一,它可用于解决以下方面的跨域数据传递:
    • a) 页面和其打开的新窗口的数据传递
    • b) 多窗口之间消息传递
    • c) 页面与嵌套的iframe消息传递
  • 用法:postMessage(data,origin) 方法接受 两个参数
    • data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用 JSON.stringify() 序列化。(另一方在用JSON.parse() 转回来)
    • origin协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
  • 样例
    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‘);
    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:(http://www.domain2.com/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、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>

Nodejs socket后台

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.on(‘message‘, function(msg) {
        client.send(‘hello:‘ + msg);  //给客户端发送信息
        console.log(‘data from client: ---> ‘ + msg);
    });

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

7、window.domain+iframe

  • 缺点:仅限主域相同 domain.com,子域不同 www和child 的跨域应用场景
  • 实现原理:把两个页面的 document.domain 设置成 主域,就实现了同域。
  • 样例
    父窗口 如: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>

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

<script>
    document.domain = ‘domain.com‘;
    // 获取父窗口中变量
    alert(‘get js data from parent ---> ‘ + window.parent.user);
</script>

8、window.name+iframe

  • window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
  • 总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
  • 样例
    • A页面
      技术分享图片
    • proxy页面,不写东西(不写window.name):proxy页面和A是同域,且无关紧要,无内容
    • B页面:B想给A的数据放在window.name中
      技术分享图片

location.hash+iframe

  • 缺点:hash有 大小限制(4kb左右),不能传复杂数据。
  • 实现原理:A想和B跨域相互通信,通过中间页C来实现。 三个页面,不同域之间利用 iframelocation.hash 传值,相同域之间直接js访问来通信。(PS:A和C同源,A和B不同源)
  • 具体实现:
    A域:a.html -> B域:b.html -> A域:c.html
    A给B信息直:A->B B给A信息:B->C->A
    A与B不同域只能通过 hash值单向通信 ,b与c也不同域也只能单向通信,但c与a同域,所以c可通过 parent.parent 访问a页面所有对象。
  • 样例
    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‘);
    //1:向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + ‘#user=admin‘; //其中‘#user=admin‘是hash值
    }, 1000);
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert(‘data from c.html ---> ‘ + res);
    }
</script>

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‘);
    //2:监听a.html传来了hash值‘#user=admin‘,hash值发生变化,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

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

<script>
    //3: 监听到b.html传来了hash值,hash变化了
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
       window.parent.parent.onCallback(location.hash.replace(‘#user=‘, ‘‘));
    };
</script>

修改浏览器的安全设置

跨域是因为浏览器的安全考虑,那就直接去改浏览器的设置啦。

参考资料

跨域及其解决方案

原文:https://www.cnblogs.com/baboon/p/13053139.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!