您的位置:首页 > Web前端

跨域问题极其常用解决方法总结

2016-12-01 10:34 204 查看
当提到跨域问题时,很多人会问,什么是跨域问题呢?这里的域指的到底是哪个域呢?

首先,什么是跨域问题?
简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。

抽象点来理解(这里打个比喻):把每个不同协议、不同域名和不同端口,不同主机名的页面,比作不同国度的人,那么页面的数据比作货币,然后问题就来了,因为每个国家的货币是不同的,所以a国家的人是不能用自己的货币去购买b国家的商品的,同样b国家也是如此。因其货币不同,本国货币去外国使用,就是跨域,而后来出现的货币兑换机制,便是解决了这一货币不通用的问题。

同样的问题,不同的域,我们的跨域,跨的当然不是国度,跨的是域名,端口,协议,以及主机名,这里只要有一个不相同, 便认为是不同的域了。

跨域访问便是,在这个域名||端口||协议||主机名,上去访问别的域名,端口,等等的数据。

其次,跨域问题的原理是什么呢?
是JavaScript同源策略的限制导致的这一问题。 同源策略限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式。

同源定义:如果两个页面拥有相同的协议(protocol),端口(如果指定),和主机,那么这两个页面就属于同一个源(origin)。

源继承:来自about:blank,JavaScript:和data:URLs中的内容,继承了将其载入的文档所指定的源,因为它们的URL本身未指定任何关于自身源的信息。

更详细的说明可以看下面的表,进行对比:



特别注意两点:
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

最后,回到正题,如何解决跨域问题?

1、document.domain+iframe的设置

页面可以改变本身的源,但会受到一些限制。脚本可以设置document.domain 的值为当前域的一个后缀,如果这样做的话,短的域将作为后续同源检测的依据。例如,假设在http://store.a.com/dir/other.html 中的一个脚本执行了下列语句:
document.domain = "a.com";
这条语句执行之后,页面将会成功地通过对 http://a.com/dir/page.html 的同源检测。
而同理,a.com 不能设置 document.domain 为b.com(其他).
浏览器单独保存端口号。任何的赋值操作,包括document.domain = documen.domain都会以null值覆盖掉原来的端口号。
因此a.com:8080页面的脚本不能仅通过设置document.domain = "a.com"就能与a.com通信。
赋值时必须带上端口号,以确保端口号不会为null。
附注:使用document.domain来安全是让子域访问其父域,需要同时将子域和父域的document.domain设置为相同的值。必须要这么做,即使是简单的将父域设置为其原来的值。没有这么做的话可能导致授权错误。
再如下:
document.domain
= 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 在这里操纵b.html
    alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
这种方式适用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}中的任何页面相互通信。
备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。

问题:
1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

2、使用jsonp(服务端代理)

服务端设置Request Header头中Access-Control-Allow-Origin为指定可获取数据的域名。jsonp的解决方式:
首先声明下:json≠jsonp
原理:
jsonp解决跨域问题的原理是,浏览器的script标签是不受同源策略限制(你可以在你的网页中设置script的src属性问cdn服务器中静态文件的路径)。
那么就可以使用script标签从服务器获取数据,请求时添加一个参数为callbakc=?,?号时你要执行的回调方法。
前端实现:
以jQuery2.1.3的ajax方法为例
代码如下:
$.ajax({
    url:"",
    dataType:"jsonp",
    data:{
        params:""
        }
}).done(function(data){
    //dosomething..
})
仅仅是客户端使用jsonp请求数据是不行的,因为jsonp的请求是放在script标签中的,和普通请求不同的地方在于,它请求到的是一段js代码,如果服务端返回了json字符串,那么浏览器是会报错的。所以jsonp返回数据需要服务端做一些处理。
服务端返回数据处理:
上面说了jsonp的原理是利用script标签来解决跨域,但是script标签是用来获取js代码的,那么我们怎么获取到请求的数据呢?
这就需要服务端做一些判断,当参数中带有callback属性时,返回的type要为application/javascript,把数据作为callback的参数执行。
下面是jsonp返回的数据的格式示例
代码如下:
/**/ typeof jQuery21307270454438403249_1428044213638 === 'function' &&

jQuery21307270454438403249_1428044213638({"code":1,"msg":"success","data":{"test":"test"}});
// jsonp
if (typeof
callback === 'string' &&
callback.length !== 0) {
this.charset = 'utf-8';
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'text/javascript');
callback =
callback.replace(/[^\[\]\w$.]/g, '');     // restrict callback charset
// 不允许用json字符串取代js代码
body = body
 .replace(/\u2028/g, '\\u2028')
 .replace(/\u2029/g, '\\u2029');

//检测类型,以免发生错误
body = '/**/ typeof ' + callback + ' === \'function\' && ' +
callback + '(' + body + ');';
}
服务端设置Access-Control-Allow-Origin:这种方式只要服务端把response的header头中设置Access-Control-Allow-Origin为制定可请求当前域名下数据的域名即可。
一般情况下设为即可。这样客户端就不需要使用jsonp来获取数据。
Access-Control-Allow-Origin带来的安全问题:
    第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,
    第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

3、动态创建script

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。
这里判断script节点加载完毕还是蛮有意思的:ie只能通过script的readystatechange属性,其它浏览器是script的load事件。以下是部分判断script加载完毕的方法。
js.onload = js.onreadystatechange = function() {
    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        // callback在此处执行
        js.onload = js.onreadystatechange = null;
    }
};

4、window.name实现的跨域数据传输

        Javascript的APIs中,如 iframe.contentWindow, window.parent, window.open 和 window.opener 允许文档间直接相互引用。
当两个文档的源不同时,这些引用方式将对 Window 和 Location对象的访问添加限制。可以使用window.postMessage
作为替代方案,提供跨域文档间的通讯。
跨域数据存储访问:存储在浏览器中的数据,如localStorage和IndexedDB,以源进行分割。每个源都拥有自己单独的存储空间,
一个源中的Javascript脚本不能对属于其它源的数据进行读写操作。
window.name属性可以用来临时存储数据,可以跨域访问。

5、使用HTML5 postMessage

估计很少人知道HTML5 APIS里有一个window.postMessage API。
window.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信。除了IE6、IE7之外的所有浏览器都支持window.postMessage。
首先,数据发送端:
我们要做的是创建通信发起端,也就是数据源”source”。
作为发起端,我们可以open一个新窗口,或创建一个iframe,往新窗口里发送数据,
简单起见,我们每6秒钟发送一次,然后创建消息监听器,从目标窗口监听它反馈的信息。
var domain = 'http://scriptandstyle.com';    //弹出一个新窗口
var myPopup = window.open(domain

            + '/windowPostMessageListener.html','myWindow');
setInterval(function(){  //周期性的发送消息
var message = 'Hello!  The time is: ' + (new Date().getTime());
console.log('blog.local:  sending message:  ' + message);
myPopup.postMessage(message,domain);
},6000);
//监听消息反馈
window.addEventListener('message',function(event) {
if(event.origin !== 'http://scriptandstyle.com') return;
console.log('received response:  ',event.data);
},false);
这里使用了window.addEventListener,但在IE里这样是不行的,因为IE使用window.attachEvent。
如果你不想判断浏览器的类型,可以使用一些工具库,比如jQuery或Dojo。
假设你的窗口正常的弹出来了,我们发送一条消息——需要指定URI(必要的话需要指定协议、主机、端口号等),消息接收方必须在这个指定的URI上。如果目标窗口被替换了,消息将不会发出。
我们同时创建了一个事件监听器来接收反馈信息。
有一点极其重要,你一定要验证消息的来源的URI!只有在目标方合法的情况才你才能处理它发来的消息。
如果是使用iframe,代码应该这样写:
//捕获iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;
//发送消息

setInterval(function(){
var message = 'Hello!  The time is: ' + (new Date().getTime());
console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
iframe.postMessage(message,domain);

},6000);
确保你使用的是iframe的contentWindow属性,而不是节点对象。
数据接收端:
下面我们要开发的是数据接收端的页面。
接收方窗口里有一个事件监听器,监听“message”事件,一样,你也需要验证消息来源方的地址。
消息可以来自任何地址,要确保处理的消息是来自一个可信的地址。
//响应事件
window.addEventListener('message',function(event) {
if(event.origin !== 'http://davidwalsh.name') return;
console.log('message received:  ' + event.data,event);
event.source.postMessage('holla back youngin!',event.origin);
},false);
上面的代码片段是往消息源反馈信息,确认消息已经收到。下面是几个比较重要的事件属性:
    source – 消息源,消息的发送窗口/iframe。
    origin – 消息源的URI(可能包含协议、域名和端口),用来验证数据源。
    data – 发送方发送给接收方的数据。
这三个属性是消息传输中必须用到的数据。
使用window.postMessage
跟其他很web技术一样,如果你不校验数据源的合法性,那使用这种技术将会变得很危险;你的应用的安全需要你对它负责。window.postMessage就像是PHP相对于JavaScript技术。
到此,跨域问题已然分享完毕,不知道你学会解决这类问题了吗?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息