[web前端]跨域通信与实验

跨域通信与实验

V1

2012-4-5

V2

2012-5-29

V2.1(优化)

2013-5-13

实验文件的使用方法见readme.txt

前言

因为浏览器的同源策略和目前大型web服务的文件分布于各域名,js中跨域通信也越来越多的出现,本文总结和实验一些跨域的方法。

实验代码:source.zip

见source目录,实验代码来自https://github.com/colorhook/crossdomain 并做了一些修改。

实验指导请见各个目录中的README

什么是跨域

什么是同源(同域)

同源策略又名同域策略是浏览器中的主要安全措施。这里的“源”指的是主机名、协议和端口号的组合。

比如:

下表给出了相对http://store.company.com/dir/page.html同源检测的结果:

URL

结果

原因

http://store.company.com/dir2/other.html

成功

 

http://store.company.com/dir/inner/another.html

成功

 

https://store.company.com/secure.html

失败

协议不同

http://store.company.com:81/dir/etc.html

失败

端口不同

http://news.company.com/dir/other.html

失败

主机名不同

在同源策略中有一个例外,脚本可以设置 document.domain 的值为当前域的一个后缀,比如域store.company.com的后缀可以是company.com。如果这样做的话,短的域将作为后续同源检测的依据。例如,假设在 http://store.company.com/dir/other.html 中的一个脚本执行了下列语句:

document.domain = “company.com”;

这条语句执行之后,页面将会成功地通过对 http://company.com/dir/page.html 的同源检测。而同理,company.com 不能设置 document.domain 为 othercompany.com

可以看到,只要1级域名相通,那么经过一定设置也可以简单通信,但是如果一级域名也不同的话只能利用一些方法进行跨域通信了。

跨域的分类

跨域访问实际上会有很多种情况,我把这里讨论的跨域通信分为两类

一类是跨域iframe访问(页面中的顶页面和iframe中的页面之间的访问)

一类是跨域访问服务器端资源(比如XHR)

条件

结果

源页面

目标

设置

iframe访问

XHR访问

a.com

a.com

 

可以

可以

a.com

s.a.com

 

不可以

不可以

a.com

s.a.com

domin设为a.com

可以

不可以

a.com

b.com

 

不可以

不可以

浏览器基于安全原因,会不允许一些跨域访问,我们就要用下面的方法来实现跨域访问。

跨域实验

先来进行一个实验,来证实一下上面说的内容

(本实验在XHR目录)

image

www.a.com的一个页面要访问四个域名下的服务器数据,

结果是 只有第一个按钮(获取www.a.com上的一个XML)能执行成功

image

其他按钮均会收到错误:

image

跨域通信的方法

新的标准

既然有跨域访问的需求,且不是很小众的要求,那么W3C就会制定相关标准。

XMLHttpRequest Level 2

与服务器通信

iframe通信

×

标准文档:http://www.w3.org/TR/XMLHttpRequest/

新设计出来的跨域方案是优秀的,IE8是XDomainRequest,Firefox3.5、Safari4、Chrome 2等是沿用原来的XMLHttpRequest对象,它们都拥有一些相同的方法处理各种回调:

函数

意义

onload

请求成功时调用。

onerror

请求失败时调用。

onabort

请求中断时调用(使用abort方法)

因此这跨域请求是非常简单了!

例子程序:

其中除了这个“if(“1″[0])”稍微有些玄幻,其他的大家应该可以看得懂。

但是这个跨域程序直接运行是不行的,还需要在服务端进行配置:

跨域资源共享(Cross-Origin Resource Sharing)

标准文档:http://www.w3.org/TR/cors/

对于浏览器来说,COR请求都是Javascript发起的,COR请求有两种:

1、简单的COR请求,它可以直接向外域资源发起请求。它必须仅仅包含简单的方法和头。

2、如果COR包含复杂的方法和头,它需要发出预检验(Preflight)请求,它先向资源服务器发出一个OPTIONS方法、包含“Origin”头的请求。该回复可以控制COR请求的方法,HTTP头以及验证等信息。只有该请求获得允许以后,才会发起真实的外域请求。

简单地说, CORS需要在请求返回的http header中包含以下内容:

设置

意义

Access-Control-Allow-Origin

允许跨域请求申请来源中的域名限制,*为不限制

Access-Control-Allow-Methods

允许请求的方法(GET、POST等)

其他

等等

下面是一个简单的COR请求:

假设这个请求所在页面的域是“http://foo.org”。 如果来自“http://bar.org/b”的回复包含这样的头:

则表明,它允许来自“http://foo.org”的跨域请求。

下面的Javascript会发出预检验请求和真实请求:

由于“Content-type: text/html”不是一个简单的头,它会先向”http://bar.org/b”发出一个OPTIONS的HTTP请求。 回复可能包含这样的头:

“Access-Control-Allow-Origin”表明它允许”http://foo.org”发起跨域请求

“Access-Control-Max-Age”表明在3628800秒内,不需要再发送预检验请求,可以缓存该结果

“Access-Control-Allow-Methods”表明它允许GET、PUT、DELETE的外域请求

“Access-Control-Allow-Headers”表明它允许跨域请求包含content-type头

如果预检验请求获得通过,接下来Javascript就会发起真实的COR请求,过程跟简单的COR请求类似。

实验

image

代码见source\access-control

JSONP

与服务器通信

iframe通信

×

讨论完了新的标准,也不能忽略不支持新标准的浏览器

Jsonp是广泛支持的一种跨域方法

浏览器虽然限制了Ajax的跨域通信,但允许在页面中插入动态的脚本元素。简单的讲就是从第三方服务器加载js代码是可行的

但加载的js代码同样被视作是从当前域加载的,所以想在第三方的js代码中进行对第三方服务器的ajax调用同样是不行的。

所以可以通过第三方服务器生成动态的js代码来回调本地的js方法,而方法中的参数则由第三方服务器在后台获取,并以JSON的形式填充到JS方法当中,这也就是“JSON with Padding”中“padding”的真正意义。

应用过程当中,请求方(本地)向第三方服务器请求动态JS脚本,并将获取数据后需要回调的函数名以约定好的参数名(如callback等)发送给第三方服务器。

第三方服务器需要为JSONP请求开发相应的API,API中先获取JSONP请求需要的数据,然后以JSON的形式封装,再与请求方的回调函数名拼接在一起,动态生成请求方需要调用的JS代码。

假设这个JSONP服务的URL为http://www.google.com/jsonp,回调的函数名为jsonpFunc,那么可以这样发送JSONP请求: (动态添加到html中)

但实际的开发中,JS库中一般都包含了更便于使用的JSONP方法,例如jquery和kissy。

以jquery为例,jsonp的调用形式如下

实验

image

代码在source/jsonp

关键部分代码(index.html)

Flash URLLoader

与服务器通信

iframe通信

×

Flash有自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API来确定自身能被哪些域的SWF加载。

当跨域访问资源时,例如从域www.a.com请求域www.b.com上的数据,我们可以借助Flash来发送HTTP请求。首先,修改域www.b.com上的crossdomain.xml(一般存放在根目录,如果没有需要手动创建) ,把www.a.com加入到白名单。其次,通过Flash URLLoader发送HTTP请求,最后,通过Flash API把响应结果传递给JavaScript。

Flash URLLoader是一种很普遍的跨域解决方案,不过需要支持iOS的话,这个方案就无能为力了。

例子程序:(actionScript)

利用form表单跨域post

现在ajax应用这么广泛,一般的应用都是直接通过异步调用就可以了,但是有些东西必须要使用post,而且是跨域的时候,ajax异步调用的方式就无能为力了。

当然现在也有很多种办法,比如通过flash中转去post,可以post到任何域中,或者是通过嵌入iframe来实现,flash的方式虽然好,但是用户还得下载个swf文件。这里用form和iframe来实现,

在这里,我通过将需要post的内容写入content的input中,然后点击提交,将form的action设置为目标服务器的url,target设置为iframe的名称,这样就可以实现无刷新的跨域post了,但是由于浏览器防止重复提交的功能,所以如果直接提交到iframe的话,后面你刷新页面的话,浏览器就会提示是否要重复提交,所以这里我们监听iframe的onload事件,当iframe成功load之后,就将iframe的src指向空白页,从而浏览器认为已经跳转到新页面了,刷新也就不会提示重复提交的弹出框了。

这里我们还可以在iframe load成功的时候,通过contenWindow属性来获取服务器的响应,从而可以判断post是否成功。

hash值实现通讯(Fragment Identifier Messaging)

与服务器通信

iframe通信

×

不同的域之间,JavaScript只能做很有限的访问和操作,其实我们利用这些有限的访问权限就可以达到跨域通信的目的了。

FIM (Fragment Identifier Messaging)就是在这个大前提下被发明的。父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为frag,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带frag,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。

image

示意图

FIM的原理就是改变URL的frag部分(hash)来进行双向通信。每个window通过改变其他window的location来发送消息,并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,URL在浏览器下有长度限制,这个制约了每次传送的数据量。

此方式可实现iframe与父网页的双向通信。

实验

image

代码见source\fragment-identitier-messaging

window.name

与服务器通信

iframe通信

×

window对象的name属性是一个很特别的属性,当该window的location变化,然后重新加载,它的name属性可以依然保持不变。

那么我们可以在页面A中用iframe加载其他域的页面B,而页面B中用JavaScript把需要传递的数据赋值给window.name,iframe加载完成之后,页面A修改iframe的地址,将其变成同域的一个地址,然后就可以读出window.name的值了。

这个方式非常适合单向的数据请求,而且协议简单、安全。不会像JSONP那样不做限制地执行外部脚本。

实验

image

代码见source\window-name

Flash 本地通信(Flash LocalConnection)

与服务器通信

iframe通信

×

√(flash之间)

页面上的双向通信也可以通过Flash来解决,Flash API中有LocalConnection这个类,该类允许两个SWF之间通过进程通信,这时SWF可以播放在独立的Flash Player或者AIR中,也可以嵌在HTML页面或者是PDF中。

遵循这个通信原则,我们可以在不同域的HTML页面各自嵌套一个SWF来达到相互传递数据的目的了。SWF通过LocalConnection交换数据是很快的,但是每次的数据量有40kb的大小限制。用这种方式来跨域通信过于复杂,而且需要了2个SWF文件,实用性不强。

window.postMessage

与服务器通信

iframe通信

×

postMessage是html5为了解决跨域通信,特别引入的一个新的API,目前支持这个API的浏览器有:Firefox, IE8+, Opera, Safari, Chrome。postMessage允许页面中的多个iframe/window的通信,postMessage也可以实现ajax直接跨域,不通过服务器端代理。

iframe1.html需要向iframe2.html发送消息,也就是第二个iframe,所以是window.parent.frames[1],如果是向父页面发送消息就是window.parent。

postMessage这个函数接收二个参数,缺一不可,第一个参数即你要发送的数据,第二个参数是非常重要,主要是出于安全的考虑,一般填写允许通信的域名,这里明河为了简化,所以使用’*’,即不对访问的域进行判断。

iframe2.html中写个监听message事件,当有消息传到iframe2.html时就会触发这个事件。

由于它是一个很新的方法,所以在较旧的浏览器中都无法使用。

实验

image

实验文件在source\window-postMessage

Cross Frame

与服务器通信

iframe通信

×

Cross Frame是FIM的一个变种,它借助了一个空白的iframe,不会产生多余的浏览器历史记录,也不需要轮询URL的改变,在可用性和性能上都做了很大的改观。

image

示意图

它的基本原理大致是这样的,假设在域www.a.com上有页面A.html和一个空白代理页面proxyA.html, 另一个域www.b.com上有个页面B.html和一个空白代理页面proxyB.html,A.html需要向B.html中发送消息时,页面会创建一个隐藏的iframe, iframe的src指向proxyB.html并把message作为URL frag,由于B.html和proxyB.html是同域,所以在iframe加载完成之后,B.html可以获得iframe的URL,然后解析出message,并移除该iframe。当B.html需要向A.html发送消息时,原理一样。

Cross Frame是很好的双向通信方式,而且安全高效,但是它在Opera中无法使用,不过在Opera下面我们可以使用更简单的window.postMessage来代替。

实验

image

代码见source\cross-frame

server proxy

与服务器通信

iframe通信

×

在数据提供方没有提供对JSONP协议或者window.name协议的支持,也没有对其它域开放访问权限时,我们可以通过server proxy的方式来抓取数据。

例如当www.a.com域下的页面需要请求www.b.com下的资源文件asset.txt时,直接发送一个指向www.b.com/asset.txt的ajax请求肯定是会被浏览器阻止。这时,我们在www.a.com下配一个代理,然后把ajax请求绑定到这个代理路径下,例如www.a.com/proxy/, 然后这个代理发送HTTP请求访问www.b.com下的asset.txt,跨域的HTTP请求是在服务器端进行的,客户端并没有产生跨域的ajax请求。

这个跨域方式不需要和目标资源签订协议,带有侵略性,另外需要注意的是实践中应该对这个代理实施一定程度的保护,比如限制他人使用或者使用频率。

各方法总结

方法

一句话说明

优点

缺点

CORS

解决跨域问题的新的标准

原生方法

旧的浏览器不支持

JSONP

创建script标签加载数据

兼容性好

需要callback

Flash URLloader

利用flash跨域加载数据

有flash就好用

没flash用不了

form跨域post

利用form跨域post

简单

只能单向发往服务器

hash

利用hash实现跨域通信

可以实现iframe跨域通信

信息量少

window.

name

利用window.name实现跨域通信

可以实现iframe跨域通信

单向

flash本地通信

两个SWF之间通过进程通信

可以实现跨iframe、页面的跨域通信

复杂,没flash用不了

window.

postMessage

新的功能,可以解决跨域iframe通信

功能完整

旧的浏览器不支持

Cross Frame

利用多个iframe进行双向跨域iframe通信

双向

复杂

server proxy

通过服务器获取数据

数据源格式不限

需消耗服务器,有侵略性

示例代码们

淘宝UED已经有建立一个代码库,可以直接去看:https://github.com/colorhook/crossdomain

跨域中的安全问题

前端的安全问题最近愈加突出,所以安全问题需要第一考虑。

因为安全问题需要前后台共同关注,这里不细说,给出几个链接:

CSRF:http://en.wikipedia.org/wiki/Cross-site_request_forgery

XSS:http://en.wikipedia.org/wiki/Cross-site_scripting

参考

Cross-Origin Resource Sharing协议的介绍

http://yaoweibin2008.blog.163.com/blog/static/1103139201110942226377/

flash.net.URLLoader (ActionScript 3.0)

http://www.actionscript.com.cn/help/flash/net/URLLoader.html

利用HTML5的window.postMessage实现跨域通信

http://www.36ria.com/3890

JavaScript的同源策略

https://developer.mozilla.org/Cn/JavaScript%E7%9A%84%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5

XMLHttpRequest Level 2的简单例子

http://www.cnblogs.com/rubylouvre/archive/2010/05/27/1744889.html

使用 window.name 解决跨域问题

http://www.planabc.net/2008/09/01/window_name_transport/

前端跨域总结

http://www.slideshare.net/zhangsuoyong/ss-10511572

学习总结:前端跨域请求的解决办法——JSONP

http://lc87624.iteye.com/blog/1123148

也来谈谈”完美”跨域

http://ued.alipay.com/wd/2008/12/17/%E4%B9%9F%E6%9D%A5%E8%B0%88%E8%B0%88%E5%AE%8C%E7%BE%8E%E8%B7%A8%E5%9F%9F/

JavaScript跨域问题分析与总结

http://www.uuxiao.com/diary_show.asp?id=632

XMLHttpRequest Level 2

http://www.w3.org/TR/XMLHttpRequest/

Cross-Origin Resource Sharing

http://www.w3.org/TR/cors/

跨域资源共享的10种方式–来源于淘宝UED

http://superchaowen.blog.163.com/blog/static/1658685452011722102827895/

跨来源资源共享

http://zh.wikipedia.org/wiki/%E8%B7%A8%E4%BE%86%E6%BA%90%E8%B3%87%E6%BA%90%E5%85%B1%E4%BA%AB

JSONP

http://zh.wikipedia.org/wiki/JSONP

利用form表单跨域post

http://hi.baidu.com/gguoyu/blog/item/08179d2340e76a489822edbb.html

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注