Cruyun's Blog


Talk is cheap, show you my code


解决跨域问题

同源策略 (Same-origin Policy )

AJAX之所以需要“跨域”,就是由于浏览器的同源策略。即一个页面的AJAX只能获取这个页面相同源或者相同域的数据。 如何叫“同源”或者“同域”呢?——协议、域名、端口号都必须相同。

当跨域请求时,一般都会看到这个错误:

1
XMLHttpRequest cannot load http://xxxxx.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

JSONP

JSONP(JSON with Padding) 是一种跨域请求方式。主要原理是利用了script 标签可以跨域请求的特性,由其 src 属性发送请求到服务器,服务器返回 JavaScript 代码,浏览器接受响应,然后直接执行,这和通过 script 标签引用外部文件的原理是一样的。

JSONP由两部分组成:回调函数和数据,回调函数一般是在浏览器控制,作为参数发往服务器端(当然,你也可以固定回调函数的名字,但客户端和服务器端的名称一定要一致)。当服务器响应时,服务器端就会把该函数和数据拼成字符串返回。

JSONP的请求过程:

  1. 请求阶段:浏览器创建一个 script 标签,并给其src 赋值(类似 http://example.com/api/?callback=jsonpCallback )。
  2. 发送请求:当给script的src赋值时,浏览器就会发起一个请求。
  3. 数据响应:服务端将要返回的数据作为参数和函数名称拼接在一起(格式类似”jsonpCallback({name: ‘abc’})”)返回。当浏览器接收到了响应数据,由于发起请求的是 script,所以相当于直接调用 jsonpCallback 方法,并且传入了一个参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 包装在一个立即调用的函数表达式
(function () {
var _callbacks = 0
// 给回调函数添加唯一标知的 id
window.jsonp = function (url, callback) {
var id = 'jsonp_cb_' + _callbacks,
existing = document.scripts[0],
// 创建script标签并加入到页面中
script = document.createElement('script')
script.src = url + (~url.indexOf('?') ? '&' : '?') + 'callback=' + id
existing.parentNode.insertBefore(script, existing)
//创建jsonp回调函数
window[id] = function (data) {
// 因为 script 标签的 src 属性只在第一次设置的时候起作用,导致 script 标签没法重用,所以每次完成操作之后要移除
script.parentNode.removeChild(script)
callback(data)
delete window[id]
}
_callbacks += 1
}
}())

这段代码将一个jsonp函数添加到window,windo接受要发送请求的URL,一旦此请求完成后回复exectue。 指定的回调附加到window,因此请求的脚本在查找和调用它时不会出现问题。 回调完成后,外部资源和 window 新附加的回调函数都将被删除。

JSONP 使用简单且兼容性不错,但是只限于 get 请求。

当然,这一段原生的 JSONP 如果要追求完整,还可以添加处理成功或失败之后的操作以及处理超时等 error 的操作。

CORS (Cross-Origin Resource Sharing)

跨源资源共享(CORS)是一种允许来自浏览器的跨域通信的W3C规范,允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS的用例很简单。 比如网站alice.com有一些网站bob.com想要访问的数据。 传统上,在浏览器的相同原始策略下不允许这种类型的请求。 但是,通过支持CORS请求,alice.com可以添加一些允许bob.com访问数据的特殊响应标头。

从这个例子可以看出,CORS支持需要服务器和客户端之间的协调。

使用 CORS 跨域的时候和普通的 ajax 过程是一样的,只是浏览器在发现这是一个跨域请求的时候会自动帮我们处理一些事情,所以说只要服务端提供支持,前端是不需要做额外的事情的

Chrome,Firefox,Opera和Safari都使用XMLHttpRequest2对象。 Internet Explorer使用类似的XDomainRequest对象,该对象的工作方式与XMLHttpRequest对应方式大致相同,但添加了额外的安全预防措施。

document.domain

获取/设置当前文档的原始域部分, 用于 同源策略.

1
2
var domainString = document.domain;
document.domain = string;

可以给document.domain赋值,但只能赋成当前的域名或基础域名。

1
2
javascript:alert(document.domain = "abc.net"); //jb51.net
javascript:alert(document.domain = "www.abc.net");

上面的赋值都是成功的,因为www.abc.net是当前的域名,而abc.net是基础域名。

但是下面的赋值就会出来”参数无效”的错误:

1
2
javascript:alert(document.domain = “cde.net”); //参数无效
javascript:alert(document.domain = "www.123.net"); //参数无效

因为cde.netwww.123.net不是当前的域名也不是当前域名的基础域名,所以会有错误出现。这是为了防止有人恶意修改document.domain来实现跨域偷取数据。

利用document.domain 实现跨域:

前提条件:这两个域名必须属于同一个基础域名,而且所用的协议,端口都要一致,也就是二级域名都相同,否则无法利用document.domain进行跨域。

比如在:aaa.com的一个网页(a.html)里面 利用iframe引入了一个bbb.com里的一个网页(b.html)。

这时在a.html里面可以看到b.html里的内容,但是却不能利用javascript来操作它。因为这两个页面属于不同的域,在操作之前,浏览器会检测两个页面的域是否相等,如果相等,就允许其操作,如果不相等,就会拒绝操作。

这里不可能把a.html与b.html利用JS改成同一个域的。因为它们的基础域名不相等。(强制用JS将它们改成相等的域的话会报跟上面一样的”参数无效错误。”)

所以如果在a.html里引入aaa.com里的另一个网页,是不会有这个问题的,因为域相等。

有另一种情况,两个子域名:a.xxx.comb.xxx.com
a里的一个网页(a.html)引入了 b 里的一个网页(b.html),这时a.html里同样是不能操作 b.html 里面的内容的。

因为document.domain不一样,一个是aaa.xxx.com,另一个是bbb.xxx.com。
这时我们就可以通过将两个页面的domain改成一样的,在a.html里与b.html里都加入: document.domain = "xxx.com";这样这两个页面就可以互相操作了。也就是实现了同一基础域名之间的”跨域”。

postMessage

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

在需要发送消息的源窗口调用postMessage方法即可发送消息。

源窗口

源窗口可以是全局的window对象,也可以是以下类型的窗口:

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

  • 当前文档窗口的父窗口:var win = window.parent;
  • 打开当前文档的窗口:var win = window.opener();

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

语法

win.postMessage('Hello', 'http://baidu.com/');

postMessage函数接收两个参数:

  1. data:要传递的数据,html5 规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。

  2. origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以将参数设置为”*”,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/“

注意只有当目标窗口的源与postMessage函数中传入的源参数值匹配时,才能接收到消息。

接收postMessage消息

要想接收到之前源窗口通过postMessage发出的消息,只需要在目标窗口注册message事件并绑定事件监听函数,就可以在函数参数中获取消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
window.onload = function() {
var text = document.getElementById('txt');
function receiveMsg(e) {
console.log("Got a message!");
console.log("nMessage: " + e.data);
console.log("nOrigin: " + e.origin);
// console.log("Source: " + e.source);
text.innerHTML = "Got a message!<br>" +
"Message: " + e.data +
"<br>Origin: " + e.origin;
}
if (window.addEventListener) {
//为窗口注册message事件,并绑定监听函数
window.addEventListener('message', receiveMsg, false);
}else {
window.attachEvent('message', receiveMsg);
}
};

可以将postMessage函数第二个参数设为*,在发送跨域消息时会跳过对发送消息的源的检查。


参考及推荐阅读: