解决跨域问题的8种方式


 

一、同源策略
 同源策略(Same origin policy)是由 NetScape 公司在1995年提出的一种安全策略;同源策略只是一个约定,提出后被各个浏览器厂商采纳,并以各自的方式实现了同源策略。
 
 同源策略:是浏览器最核心也最基本的安全功能,会约束浏览器的行为;
 同源策略会限制浏览器:只允许本域内的脚本读写本域内的资源,不允许访问本域外的资源。

判断是否同源?
 判断要素有三:协议、域名、端口号;
 (三者全部一致才视为同源,即属于同一个域;否则视为非同源。)

限制范围
 无法共享 cookie, localStorage, indexDB。
 无法操作彼此的 dom 元素。
 无法发送 ajax 请求。

二、跨域


 域(Domain)是Windows网络中独立运行的单位;
 域之间相互访问则需要建立信任关系,信任关系是连接在域与域之间的桥梁;
 当一个域与其他域建立了信任关系后,2个域之间不但可以按需要相互进行管理,还可以跨网分配文件和打印机等设备资源,使不同的域之间实现网络资源的共享与管理.


跨域:突破同源策略的限制,在两个不同的域之间(非同源页面)实现资源交互。

跨域分类:
 1)使用 Ajax 引发的跨域问题
当调用 Ajax 时:调用 Ajax 发送请求的页面 所在的域,和被请求页面所在的域不一致
 2)类似页面嵌入 ifream 引起的跨域问题
当操作 ifream 内引入的元素时:ifream 所属页面的域,和 ifream 引入页面的域不一致


三、跨域实现方法?

1. JSONP:
 JSONP 就是跨域的一种实现方式。(JSONP 全称 JSON with Padding,是数据格式 JSON 的一种 “使用模式”,可用于解决主流浏览器的跨域数据访问的问题。)
 JSONP 是一种非正式传输协议,该协议的一个要点就是允许用户传递一个 callback 参数给服务端,然后服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

JSONP 的原理:
 利用 script 标签不受同源策略影响,可以跨域引入外部资源的特性,让服务器端返回可执行的 JS 函数,将要返回的数据作为参数传进函数,以此实现跨域加载数据的目的
 此时,绕过 Ajax,并未使用它,但同样达成了请求数据的目的

[](【备注】——script 标签引用资源得本质是:
 1)向 src 发送请求
 2)将资源下载到当前页面
 3)当资源加载完毕后,把该资源当做 JS 代码来立刻执行——【备注】)

JSONP 的使用:

 1、动态创建 script 标签,src 地址指向数据接口,并传递 callback 参数
 2、定义数据处理函数
 3、服务端接收请求,解析参数,计算数,返回回调函数字符串
 4、将回调函数字符串引入页面并作为 JS 去执行:此时会调用数据处理函数,数据会作为数据处理函数的参数被处理计算出一个结果


JSONP 的优缺点
 优点
 1)因 script 隶属于 HTML 的标签,所以不存在兼容问题

 缺点
 1)因需使用 URL 引入资源,所以 JSONP 仅支持 get 请求
 2)因 script 标签会将资源作为 JS 代码执行,所以可能会被注入恶意代码


2、CORS
 CORS 全称 Cross-Origin Resource Sharing,即:跨来源资源共享。它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是JSONP模式的现代版


CORS 的原理:
 1、当使用 XMLHttpRequest 发送请求时,如果浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin;
 2、后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin;
 3、浏览器判断该响应头中是否包含 Origin 的值:
 4、如果包含浏览器则会处理响应,前端就可以拿到响应数据;
 5、如果不包含浏览器直接驳回,此时前端无法拿到响应数据。
 (简单来说,就是浏览器匹配请求头和响应头,如果符合要求便可拿到数据,否则无法拿到数据。整个过程都是由浏览器自动完成。这就像一个白名单,代表着谁可以拿到数据。)


CORS 的使用:
 前端:正常使用 AJAX 发送请求
 服务端:若确定接受请求,则在返回结果中加入响应头:Access-Control-Allow-Origin


CORS 的优缺点:
 优点
 1)使用简单方便、更为安全
 2)支持 POST 请求方式

 缺点
 1)CORS 是一种新型跨域问题的解决方案:存在兼容问题——仅支持 IE10 以上

3、降域
 降域仍是解决跨域问题的一种方案,通过双向设置 document.domain 的值,解决主域名下的跨域问题


降域的原理
 比如,有两个二级域名:a.yang.com 和 b.yang.com,可通过设定 document.domain 的值为主域名:yang.com 的方式,突破浏览器的同源策略限制,来获取和操作对方的元素

 这就好比,小 A 和小 B,手里拿着城主的令牌,通过哨卡时才能畅行无阻,否则哨卡不让过

 这也就决定了,降域具有很大的局限性,适用范围较小,适合在同一主域名下使用;需要有降的空间,方可使用降域方式

降域的使用
 A 页面域为:a.yang.com

 B 页面域为:b.yang.com

 A 和 B 两页面都需加入该行代码:document.domain = 'yang.com'; ,‘yang.com 是 a.yang.com 和 b.yang.com 的主域名

4、postMessage
 那么当两个主域名完全不同时,应该如何处理呢?来看看新方法 postMessage。

 postMessage 原理
 postMessage 是 HTML5 中新增方法,可实现跨域通信;

 postMessage 并不是向服务器读写资源,只是向外发送消息而已;可以把它当做使用手机发送短信消息,仅此而已。

 也就是:A 页面向 B 页面发送了一条消息,B 页面会接受到该消息,如果 B 页面需要该消息,则监听 message;否则无需关心该消息

 postMessage 的使用
 发送方:为目标元素添加事件处理程序,监听事件类型

 接收方:为 window 添加事件处理程序,事件类型为 messag


postMessage 的注意点
 这样就解除了降域的限制,可以将 postMessage 应用到各个不同的域之间,实现跨域访问。该方式比较安全,因为对方并不能直接操控我方资源,仅仅是发了一条消息,相当于指令,而最终操作权限仍在自己手中
 虽然解决了降域的限制,但是:postMessage 是 window 的一个方法,话句话说:它的弱点是只能向 window 窗口发送消息。所以使用时至少要有一个 window 才行,postMessage 不可以向其他域名发送消息

(注意) 
 最后:降域和 postMessage 都是小众的降域方式,并不是经常使用,若有需求可按需选择


5、nginx代理跨域(服务器代理)

 跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

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

#proxy服务器
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;
    }
}

6、 WebSocket协议跨域

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

 1.)前端代码:
<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、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>


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

1.)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(跨域页)成功后,切换到同域代理页面
            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);
});
2.)proxy.html:(http://www.domain1.com/proxy....
中间代理页,与a.html同域,内容为空即可。

3.)b.html:(http://www.domain2.com/b.html)

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM