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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pedrojuliet/article/details/53419064

当提到跨域问题时,很多人会问,什么是跨域问题呢?这里的域指的到底是哪个域呢?

首先,什么是跨域问题?

简单地理解就是因为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;

    }

};

4window.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技术。

到此,跨域问题已然分享完毕,不知道你学会解决这类问题了吗?

展开阅读全文

没有更多推荐了,返回首页