跨域

同源策略和跨域

浏览器的同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。同源是指:协议相同,域名相同,端口相同。同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

非同源限制: 无法读取Cookie、LocalStorage 和 IndexDB, 无法获得DOM, 不能发送AJAX 请求。

JSONP(JSON Padding)

jsonp在页面上引入不同域上的js脚本文件,利用了script标签的src属性是没有跨域的限制的,从而达到跨域访问的目的。因此它的最基本原理就是:动态添加一个<script>标签来实现。

实现

这里是使用ajax来请求的,看起来和ajax没啥区别,其实还是有区别的。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

1
2
3
4
5
6
7
8
9
$.ajax({  
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
// some code
}
});

上面的代码中,callback是必须的,callback是什么值要跟后台拿。获取到的jsonp数据格式如下:

1
2
3
4
5
flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});

jsonp的全称为json with padding,上面的数据中,flightHandler就是那个padding.

特性

  • 只能使用get方法,不能使用post方法:

script,link, img, iframe等具有src属性的标引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get 的。

CORS策略

CORS策略

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。它为Web服务器定义了一种方式,允许网页从不同的域访问其资源.

实现方法:

CORS需要浏览器和服务器同时支持

  • 前端方面

以前我们使用Ajax,代码类似于如下的方式:

1
2
3
4
var xhr = new XMLHttpRequest();
// 这里的“/hfahe”是本域的相对路径。
xhr.open("GET", "/hfahe", true);
xhr.send();

如果我们要使用CORS,相关Ajax代码可能如下所示:

1
2
3
4
var xhr = new XMLHttpRequest();
// 请注意,代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是要跨域访问的接口地址。
xhr.open("GET", "http://blog.csdn.net/hfahe", true);
xhr.send();
  • 服务器方面

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

特点

  • CORS支持所有类型的HTTP请求。

document.domain+iframe

使用条件:主域相同

浏览器中不同域的框架之间是不能进行js的交互操作的。但是不同的框架之间(父子或同辈),是能够获取到彼此的window对象的。

比如,有一个页面的地址是 http://www.example.com/a.html , 在这个页面里面有一个iframe,它的src是 http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以无法通过在页面中书写js代码来获取iframe中的东西的。这个时候,document.domain就可以派上用场了,只要把 http://www.example.com/a.htmlhttp://example.com/b.html 这两个页面的document.domain都设成相同的域名就可以了。

但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.comb.example.comexample.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是
当前域的子域,也不可以设成baidu.com,因为主域已经不相同了.

实现

比如在http://www.example.com/a.html 的页面里要访问 http://example.com/b.html里面的东西。在页面 http://www.example.com/a.html 中设置document.domain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//http://www.example.com/a.html
<html>
<head>
<title>A页面</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div>A页面</div>
// 相当于用一个隐藏的iframe来做代理
<iframe id="iframe" src="http://example.com/b.html" style="display:none;"></iframe>
<script>
$(function(){
try{
document.domain = "example.com"; //这里将document.domain设置成一样
}catch(e){}
$("#iframe").load(function(){
var iframe = $("#iframe").contentDocument.$;
ifram.get("http://example.com/接口",function(data){});
});
});
</script>
<body>
</html>

在页面 http://example.com/b.html 中也设置document.domain,而且这也是必须的,虽然这个文档的domain就是example.com,但是还是必须显示的设置document.domain的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//http://example.com/b.html
<html>
<head>
<title>B页面</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div>B页面</div>
<script>
$(function(){
try{
document.domain = "example.com"; //这里将document.domain设置成一样
}catch(e){}
});
</script>
</body>
</html>

这里有个注意点,就是在A页面中,要等iframe标签完成加载B页面之后,再取iframe对象的contentDocument,否则如果B页面没有被iframe完全加载,在A页面中通过contentDocument属性就取不到B页面中的jQuery对象。一旦取到B页面中的jQuery对象,就可以直接发ajax请求了,这种类似“代理”方式可以解决主子域的跨域问题

HTML5 postMessage

HTML5 window.postMessage是一个安全的、基于事件的消息API

在需要发送消息的源窗口调用postMessage方法即可发送消息。其中, 源窗口可以是全局的window对象,也可以是以下类型的窗口:

  • 文档窗口中的iframe:
1
2
var iframe = document.getElementById('my-iframe');
var win = iframe.documentWindow;
  • JavaScript打开的弹窗:
1
var win = window.open();
  • 当前文档窗口的父窗口:
1
var win = window.parent;
  • window.opener
1
var win = window.opener();

发送消息:找到源window对象后,即可调用postMessage API向目标窗口发送消息:

1
win.postMessage(msg, targetOrigin);

接收消息:那目标窗口要怎么接收传过来的数据呢,只要监听window的message事件就可以接收了。

1
2
3
4
5
6
7
8
9
10
11
var onmessage = function (event) {
var data = event.data;
var origin = event.origin;
//do someing
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}

实现

  • http://test.com/index.html —> 发送消息的页面
1
2
3
4
5
6
7
8
9
10
11
<!-- 这个是 http://test.com/index.html 页面 -->

<div>
<!-- 要给下面的页面传一个妹子过去 -->
<iframe id="child" src="http://lsLib.com/lsLib.html"></iframe>
</div>
<script type="text/javascript">
window.onload=function(){
window.frames[0].postMessage('xxx','http://lslib.com');
}
</script>
  • http://lslib.com/lslib.html —> 接收消息的页面
1
2
3
4
5
6
7
<!-- 这个是 http://lslib.com/lslib.html 页面 -->
<script type="text/javascript">
window.addEventListener('message',function(e) {
console.log(e.origin,e.data);
alert('收到妹子一枚:'+e.data);
});
</script>

window.name

原理:当iframe的页面跳到其他地址时,其window.name值保持不变,并且可以支持非常长的 name 值(2MB)。

浏览器跨域iframe禁止互相调用/传值.但是调用iframe时 window.name 却不变,正是利用这个特性来互相传值,当然跨域下是不容许读取ifram的window.name值.所以这里我们还要准备一个和主页面http://www.a.com/main.html 相同域下的代理页面http://www.a.com/other.html ,iframe调用子页面http://www.b.com/data.html

实现

  • 准备三个页面:

    • http://www.a.com/main.html //应用页面
    • http://www.a.com/other.html // 代理页面,要求和应用页面在同一个域。一般是一个空的html
    • http://www.b.com/data.html //应用页面获取数据的页面,简称:数据页面
  • 数据页面将数据传到window.name中去。

http://www.b.com/data.html中的 data.html

1
2
// data.html
window.name="xxx"; //可以是其他类型的数据,比如数组,对象等等

应用页面 http://www.a.com/main.html 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- main.html -->
var iframeData;

var state = 0;//开关变量

var iframe = document.createElement('iframe'); //创建iframe

var loadfn = function() {

if (state === 1) {

iframeData = iframe.contentWindow.name; // 读取数据

alert('获取到了iframe传过来的妹子'+iframeData);

}else if (state === 0) {

state = 1;
iframe.contentWindow.location = 'http://www.a.com/other.html'; //这里是代理页面 other.html

/**
这里说明一下:
由于iframe的location改变了,相当于重新载入页面(这是iframe的性质决定的),于是重新执行loadfn方法。
        由于当iframe的页面跳到其他地址时,其window.name值保持不变,并且此时开关变量 state已经变为1,
于是就可以获取到window.name值,也就达到了跨域访问的目的了。

**/

};
}
iframe.src = 'http://www.b.com/data.html'; //这是是数据页面,data.html

if (iframe.attachEvent) {

iframe.attachEvent('onload', loadfn);

} else {

iframe.onload = loadfn;
}
document.body.appendChild(iframe);
  • 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。
1
2
3
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);

WebSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信