跨域通信总结

由于出于安全方面的考虑,JavaScript有着跨源策略的限制,也就是某个域的网页只能对同域进行访问。本文记录几种跨域实现及我自己的实践。

本文总结以下方式实现跨域通信:

  1. 代理服务器
  2. 图像Ping
  3. iframe技术(iframe+document.domain;iframe+location.hash)
  4. window.name
  5. HTML5跨文档消息传递(XDM)
  6. JSONP
  7. CORS(跨站资源共享)

    1. 代理

所谓代理,就是用后台技术实现代理服务器,前端访问代理服务器,代理服务器向实际服务器发起请求,拿到数据再返回给前端。 例如一个网站有两个服务器www.Chongqing.com和www.Hangzhou.com,在杭州的话想访问重庆服务器的资源,先直接访问杭州服务器,杭州服务器再从重庆服务器拉取数据最后返回给杭州的user-agent.

2. 图像ping

虽然js有同源现在,但是,在html里有几个元素可以没有限制的访问任何网站的内容,比如 img,iframe,script ,哈哈哈哈哈哈,那么,就可以利用这一点实现跨域咯。

图像ping用于客户端和服务端进行单向的简单的通信,只能发送get请求,无法访问服务器响应文本,在这里不详解;

1
2
var img = new Image();
img.src = 'http://www.luoxia520.com?name=luoxia';

3. iframe

iframe+document.domain:

对于来自同一域名下的不同子域名,可以通过设置document.domain为相同值来实现跨域通信:

1
2
3
4
//www.luoxia520.com和p2p.luoxia520.com

//在两者的index.html下设置:
document.domain = 'luoxia520.com';

然后就可以通过內建iframe操作异域的对象了:

1
2
3
4
5
6
7
//www.luoxia520.com
var ifr = document.createElement('iframe');
ifr.src = 'prp.luoxia520.com';
ifr.syle.display = 'none';
document.body.appendChild(ifr);
var otherDoc = ifr.contentDocument||ifr.contentWindow.document;
//其他操作

location.hash和iframe:

location.hash值表示url地址的哈希值,如www.luoxia520.com#luoxia中#luoxia就是hash,改变某个网页的hash并不会刷新页面,我们可以在异域的iframe中直接改变ifame拥有者的hash值,但是有些浏览器是不允许异域的iframe改变其parent的hash的,我们可以增加一个代理的网页,这个网页和父级框架是同域的,然后这个代理框架就可以修改父级的hash了:

发送方www.baidu.com/index.html:

1
2
3
4
5
6
7
8
9
10
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,
// 所以要利用一个中间的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'www.luoxia520.com/proxy.html#somedata'; // 注意该文件在"a.com"域下
document.body.appendChild(ifrproxy);
}

接受方www.luoxia520.com:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'htttp://www.baidu.com';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
} catch(e) {};
}
setInterval(checkHash, 2000);

代理方www.luoxia520.com/proxy.html:

1
2
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

4. window.name:

window下有一个全局的name属性,出于安全方面考虑,某个页面只能访问与其同域下的网页的window.name属性。

对于一个框架,当其加载某个页面时,这个页面的window.name属性会报错在这个frame下,现在当它加载另一个页面时,这个时候frame保存的windwo.name属性将不会发生变化。

利用上述原理,我们可以在将某个页面的iframe的src指向异域的页面,获取到其window.name后,我们再将iframe的src指向一个同源的‘代理’页面,这样我们就可以访问到这个window.name了:

发送消息:www.baidu.com:

1
2
3
4
5

<script type="text/javascript">
window.name = 'I was there!'; // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
</script>

接受消息:www.luoxia520.com/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/javascript">
var state = 0,
iframe = document.createElement('iframe'),
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'I was there!'
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = "http://www.luoxia520.com/proxy.html"; // 设置的代理文件
}
};
iframe.src = 'http://b.com/data.html';
iframe.onload = loadfn;
document.body.appendChild(iframe);
</script>

5.HTML5跨文档消息传递(XDM):

跨文档消息传递是HTML5提供的用于网页文档之间互相接收和发送消息的功能

发送消息

1
2
otherWindow.postMessage(message,targetOrigin);
//message可以是字符串,js对象可通过转换成json字符串的形式传递

otherWindow是指包含其他页面的内联框架的contentWindow,targetOrigin是规定接受方页面必须来自于哪个域,*表示允许所以域,如果满足要求,则会向内联框架内的页面发送message
接收信息

1
window.addEventListener('message',function(event){...},false);

这里window是内联框架指向的页面的window对象,message事件的回调函数的event对象参数有三个属性:data(数据),origin(发送方的域),source(发送方的window对象代理),这个source只是发送方window的代理,只用于调用postMessage方法,用于向发送方回复消息。

6. JSONP

啥是JSONP? JSON with padding(参数式json),实际上是对json的一种应用方法,将json数据传给回调函数,JSONP是通过script元素设计的,如下:

1
2
3
4
5
6
<script src='www.luoxia520.com/blog/?callback=callback'></script>
<script>
function callback(data){
console.log(data.name);
}
</script>

这里通过script可加载任意域内容的特点对异域进行强求,查询字符串包含callback参数,服务器传回的是可执行的js代码,即对于的callback(data)调用。当然服务端代码也要响应的更改。下面介绍。

这里以jquery的ajax为例:

  1. 前端ajax请求代码: testJsonp.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $(function(){
    $.ajax({
    dataType: 'jsonp',
    url: 'http://localhost:8088',
    type:'get',
    success: function(data){
    $('#name').text(data);
    }
    });
    });
  2. 用Nodejs实现得异域服务器代码:

    1
    2
    3
    4
    5
    6
    7
    8
    var http = require('http');
    var urlLib = require('url');
    var data = {name:'luoxia',year:18};
    http.createServer(function(req,res){
    var parms = urlLib.parse(req.url,true);
    var str = parms.query.callback + '(' + JSON.stringify(data) +')';
    res.end(str);
    }).listen(8088);

jquery的ajax也是利用script的原理,只不过只要设置了dataType为jsonp的话,就自动回发送jsonp请求,我们可以看到testJsonp.html请求的URL:
jsonp
看到了吧,自动加上了callback查询字符串,callback的值是动态生成的,当然也可以指定其值,没啥影响,还有,前面的callback这个,也可以改的,通过jsonp属性指定,当然这是细节。

nodejs通过查询callback值,即回调函数名,然后返回传递了json数据的可执行js字符串。

最后成功地实现了跨域,哈哈哈哈哈哈哈哈。
luoxia

jsonp只能发送get请求。

当然这里开始遇到了个问题,console老是报错

Failed to load resource: net::ERR_CACHE_MISS

,好大一半天不知道咋解决,后面在论坛的悠悠建议下,给ajax请求url加了个协议,丫的,成了。。。或许无法自动补全吧,细节啊。。。。

7. CORS

CORS即跨源资源共享,是W3C的草案,定义了必要时浏览器和异域服务器该如何进行跨域的沟通。CORS与JSONP相比,无疑更为先进、方便和可靠。
  1. JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。

  2. 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。

  3. JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS

要求请求头加入

1
Origin: host:port

响应头必须加入和请求头同样host:port的响应信息:

1
Access-Control-Allow-Origin: host:port

IE8以下的就别想了,IE8,IE9可以通过XDR实现,反正我觉得,垃圾IE,现在微软自己都放弃治疗了。。。。。。

来具体看看实现例子,还是jquery的ajax:

前端testCORS.html:

1
2
3
4
5
6
7
8
9
$(function(){
$.ajax({
url: 'http://localhost:8088',
type:'get',
success: function(data){
$('#name').text(JSON.parse(data).name);
}
});
});

Node: CORS.js

1
2
3
4
5
6
7
8
var http = require('http');
//var urlLib = require('url');
var data = {name:'luoxia',year:18};
http.createServer(function(req,res){
var parms = urlLib.parse(req.url,true);
res.writeHead(200,{'Access-Control-Allow-Origin':'*','Access-Control-Allow-Method':'GET,POST'});
res.end(JSON.stringify(data));
}).listen(8088);

这里Access-Control-Allow-Origin可以设置为*代表允许说有域,当然也可以写具体的url。
最终是没问题的:

CORS一定程度上缓解了CSRF,XSS等安全问题。

明天就5.20,国际计量节!单身狗过国际计量节,new对象去了。。。

罗峡的博客 wechat
欢迎扫描上面的微信公众号二维码,关注我的个人公众号:全栈前端
坚持原创技术分享,您的支持将鼓励我继续创作!