js跨域

跨域

只要协议、域名、端口有一项不同,都被当做是不同的源。

同源策略以源为边界,把资源分隔开,从而保护用户的信息安全。

JSONP

jsonp不是ajax请求,ajax是异步的,jsonp是同步的。

用于解决AJAX跨域的一种方案。

用jquery实现JSONP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
url: "http://tonghuashuo.github.io/test/jsonp.txt",
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: 'dosomthing'
})
.done(function(res) {
console.log('success');
console.log(res);
})
.fail(function(err) {
console.log('error');
console.log(err);
})

完整的请求:

http://tonghuashuo.github.io/test/jsonp.txt?callback=dosomething&_=1471419449018。

三个参数:

  • dataType: ‘jsonp,用于表示这是一个jsonp请求
  • jsonp:’callback’,用于告诉服务器根据这个参数获取回调函数的名称,通常约定就为callback
  • jsonpCallback:’dosomthing’,回调函数的名称,也是callback参数的值

其中:

(1)jsonpCallback参数可以忽略,jquery会随机生成一个字符串作为函数名,推荐这么做,以减少不必要的命名,同时派出潜在的安全隐患。忽略jsonpCallback的同时,jsonp参数必须指明,不能为false。

1
2
3
4
5
6
7
8
9
$.ajax({
url: "http://tonghuashuo.github.io/test/jsonp.txt",
dataType: 'jsonp',
jsonp: "callback"
})
// 这样会发出如下结构的请求
// http://tonghuashuo.github.io/test/jsonp.txt?callback=jQuery31008590081461589807_1471506026601&_=1471506026602
// 可以看到 jQuery 自动创建了一个随机字符串作为 callback 参数的值

(2)jquery还支持将jsonp设置为false来避免callback出现在请求URL中,但需要前后端配合,前端必须要指定jsonpCallback的值为一个函数名,后端由于无法从请求中获取回调函数的名称,因此也必须固定使用同名的字符串进行拼接。

1
2
3
4
5
6
7
8
9
10
11
$.ajax({
url: "http://tonghuashuo.github.io/test/jsonp.txt",
dataType: 'jsonp',
jsonp: false,
jsonpCallback: "myCallback"
})
// 这样会发出如下结构的请求
// http://tonghuashuo.github.io/test/jsonp.txt?_=1471506026602
// 可以看到 callback 参数被隐藏了,单从 URL 上看不容易看出这是一个 JSONP 请求
// 后端也无法从请求中获取回调函数名,因此必须事先约定好回调函数名,例如大家都使用 myCallback

后台:从数据库获取请求的资源,构造JSON形式的返回内容–>根据url中的callback参数书的值,以字符串凭借的方式,构造出一个“javaScript函数调用”的字符串。将准备返回的json作为参数放入括号中。

前端:响应内容传回前端时,jquery会自动接管,将其中的json部分玻璃出来给successerror

纯javaScript实现jsonp

1
2
3
4
5
6
7
<script>
var url = 'http://tonghuashuo.github.io/test/jsonp.txt?callback=dosomething&_=1471419449018
var script = document.createElement('script');
script.setAttribute('src',url);
document.getElementByTagName('body')[0].appendChild(script);
</script>

jsonp的原理

ajax受到“同源政策”的限制,但是带有src<script><img><iframe>是不受限制的。因此我们可以通过向页面中动态添加<script>标签来完成对跨域资源的访问。

jsonp的优缺点

优点:

兼容性好,较老的浏览器上也可以使用。

缺点:

1、只能进行GET请求,不能进行POST请求,因为是通过<script>引用的资源,参数全部显示的放在URL中,和ajax没有关系。

2、存在安全隐患,动态插入<script>标签其实就是一种脚本注入。

子域不同主域相同(document.domain

一个页面地址为http://www.damonare.cn/a.html,他的页面里面有一个iframe,它的src为http://damonare.cn/b.html

这个页面与它里面的iframe看框架是不同的域,所以无法通过在页面中书写js代码来获取iframe中的东西。

解决办法:

利用document.domain,把两个页面的document.domain都设成相同的域名。但document.domain的设置有限制,只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

1
2
3
4
5
6
7
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'damonare.cn';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
}
</script>
1
2
3
<script type="text/javascript">
document.domain = 'damonare.cn';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

通过CORS跨域

实现CORS通信的关键是服务器,服务期实现了CORS接口,就可以跨域通信。

服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。

CORS与JSONP对比:

  • JSONP只能进行GET请求,而CORS支持所有类型的HTTP请求;
  • JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。

通过HTML5的postMessage方法跨域

主要使用接受信息的message和发送信息的postMessage方法。

比如aa.cn域的A页面通过iframe嵌入一个google.com域的B页面,可以通过这一方法通信。

A页面通过potMessage发送消息:

1
2
3
4
5
6
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://www.google.com';
ifr.contentWindow.postMessage('hello, world!', targetOrigin);
}
  • ifr:表示目标窗口;
  • hello, world!:要发送的消息,类型为String、Object
  • targetOrigin: 限定的消息发送范围,不限制时使用*

B页面通过message事件监听并接受消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var onmessage = function(event) {
var data = event.data; // 消息
var origin = event.origin; // 消息来源地址
var source = event.source; // 源window对象
if(origin === 'http://www.google.com') {
console.log(data);
}
}
if(typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else {
window.attachEvent('onmessage', onmessage); // ie兼容
}

window.name + iframe

设置了window.name,即使页面用location.href实现了跳转,这个window.name还是会保留。

$(iframe).contentWindow来拿到iframe中的window对象,属性会很少,但如果外面的域和iframe的域相同时,这个ifram就是window的完整对象,就可以访问它的所有属性。

应用:

1
2
3
4
5
6
7
iframe: b.com/index.html
window: a.com/index.html
window.name = 'test';
location.href = 'a.com/empty.html'; // 与外部处于同一域下
// 此时内外同域,可使用:
$(iframe).contentWindow.name