几种方法解决 JavaScript 跨域问题

发布于 2017-10-03 17:20:41 字数 9590 浏览 2302 评论 0

JavaScript 出于安全方面的考虑,不允许跨域调用其他页面的对象。但在安全限制的同时也给注入 iframe 或是 ajax 应用上带来了不少麻烦。这里把涉及到跨域的一些问题简单地整理一下。

几种方法解决 JavaScript 跨域问题

什么是跨域

首先什么是跨域,简单地理解就是因为 JavaScript 同源策略的限制,a.com 域名下的 JavaScript 无法操作 b.com 或是 c.a.com 域名下的对象。更详细的说明可以看下表:

URL说明是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口不允许
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议不允许
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名对应ip不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同不允许
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上)不允许(cookie这种情况下也不允许访问)
http://www.he11oworld.com/a.js
http://www.a.com/b.js
不同域名不允许

特别注意两点:

  • 如果是协议和端口造成的跨域问题“前台”是无能为力的,
  • 在跨域问题上,域仅仅是通过 URL的首部 来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。URL的首部指 window.location.protocol + window.location.host,也可以理解为 Domains, protocols and ports must match。

document.domain + iframe的设置

对于主域相同而子域不同的例子,可以通过设置 document.domain 的办法来解决。 具体的做法是可以在 http://www.a.com/a.htmlhttp://script.a.com/b.html 两个文件中分别加上 document.domain = 'a.com' 然后通过 a.html 文件中创建一个 iframe,去控制 iframecontentDocument ,这样两个 JS 文件之间就可以 “交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把 script.a.comdomian 设为 alibaba.com 那显然是会报错地,代码如下:

www.a.com 上的 a.html

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);
};

script.a.com 上的 b.html

document.domain = 'a.com';

这种方式适用于 www.kuqin.comkuqin.comscript.kuqin.comcss.kuqin.com 中的任何页面相互通信。

备注:某一页面的 domain 默认等于 window.location.hostname。主域名是不带 www 的域名,例如 a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如 www.a.com 其实是二级域名。 domain 只能设置为主域名,不可以在 b.a.com中将 domain 设置为 c.a.com

问题:

  • 安全性,当一个站点 b.a.com 被攻击后,另一个站点 c.a.com 会引起安全漏洞。
  • 如果一个页面中引入多个 iframe,要想能够操作所有 iframe,必须都得设置相同 domain

动态创建 Script

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的 JS 文件中的 function(包括操作 cookie、Dom 等等)。根据这一点,可以方便地通过创建 script 节点的方法来实现完全跨域的通信。具体的做法可以参考 YUI 的 Get Utility

这里判断 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;
	}
};

利用 iframe 和 location.hash

这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用 location.hash 来进行传值。在 http://a.com#helloword 中的 #helloworld 就是 location.hash,改变 hash 并不会导致页面刷新,所以可以利用 hash 值来进行数据传递,当然数据容量是有限的。假设域名 a.com 下的文件 cs1.html 要和 he11oworld.com 域名下的 cs2.html 传递信息,cs1.html 首先创建自动创建一个隐藏的 iframeiframesrc 指向 he11oworld.com 域名下的 cs2.html 页面,这时的 hash 值可以做参数传递用。cs2.html 响应请求后再将通过修改 cs1.htmlhash 值来传递数据(由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值,所以要借助于 a.com 域名下的一个代理 iframe,但是 Firefox 可以修改)。同时在 cs1.html 上加一个定时器,隔一段时间来判断 location.hash 的值有没有变化,一点有变化则获取获取 hash 值。代码如下:

先是 a.com 下的文件 cs1.html 文件:

function startRequest(){
var ifr = document.createElement('iframe');
	ifr.style.display = 'none';
	ifr.src = 'http://run.wenjiangs.com/code/#/#paramdo';
	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);

he11oworld.com 域名下的 cs2.html:

//模拟一个简单的参数处理操作
switch(location.hash){
	case '#paramdo':
		callBack();
		break;
	case '#paramset':
		//do something……
		break;
}

function callBack(){
	try {
		parent.location.hash = 'somedata';
	} catch (e) {
		// ie、chrome的安全机制无法修改parent.location.hash,
		// 所以要利用一个中间的he11oworld域下的代理iframe
		var ifrproxy = document.createElement('iframe');
		ifrproxy.style.display = 'none';
		ifrproxy.src = 'http://www.wenxiyan.org/#somedata'; // 注意该文件在"a.com"域下
		document.body.appendChild(ifrproxy);
	}
}

a.com 下的域名 cs3.html

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

当然这样做也存在很多缺点,诸如数据直接暴露在了 url 中,数据容量和类型都有限等……

window.name 实现的跨域数据传输

有三个页面:

  • a.com/app.html:应用页面。
  • a.com/proxy.html:代理文件,一般是一个没有任何内容的 html 文件,需要和应用页面在同一域下。
  • b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。

实现起来基本步骤如下:

在应用页面 a.com/app.html 中创建一个 iframe,把其 src 指向数据页面 b.com/data.html
数据页面会把数据附加到这个 iframewindow.name 上,data.html代码如下:

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

在应用页面(a.com/app.html)中监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)。app.html部分代码如下:

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://a.com/proxy.html";    // 设置的代理文件
	}  
};
iframe.src = 'http://b.com/data.html';
if (iframe.attachEvent) {
	iframe.attachEvent('onload', loadfn);
} else {
	iframe.onload  = loadfn;
}
document.body.appendChild(iframe);

获取数据以后销毁这个 iframe,释放内存,这也保证了安全,不被其他域frame js访问。

iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);

总结起来即:iframesrc 属性由外域转向本地域,跨域数据即由 iframewindow.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

使用 HTML5 postMessage

HTML5中最酷的新功能之一就是 跨文档消息传输 Cross Document Messaging。 下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook 已经使用了这个功能,用 postMessage 支持基于 Web 的实时消息传递。

  • otherWindow:对接收信息页面的 window 的引用。可以是页面中 iframecontentWindow 属性,window.open 的返回值,通过 name 或下标从 window.frames 取到的值。
  • message:所要发送的数据 string 类型。
  • targetOrigin:用于限制 otherWindow* 表示不作限制

a.com/index.html 中的代码:

<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://b.com';  // 若写成'http://b.com/c/proxy.html'效果一样
                                        // 若写成'http://c.com'就不会执行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b.com/index.html 中的代码:

<script type="text/javascript">
    window.addEventListener('message', function(event){
        // 通过origin属性判断消息来源地址
        if (event.origin == 'http://a.com') {
            alert(event.data);    // 弹出"I was there!"
            alert(event.source);  // 对a.com、index.html中window对象的引用
                                  // 但由于同源策略,这里event.source不可以访问window对象
        }
    }, false);
</script>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文