今天终于有时间把senocular上关于安全域和应用程序域的教程好好看了一遍。觉得人家老外就是专业:内容非常有条理且完整,图文并茂,举例也非常实用,真是教程中的精品。刚好我最近也在整理这方面的知识,于是决定把这篇翻译出来,方便国内的读者。对想要进阶理解Flash的运行机制的朋友,本文是不可多得的好材料。
原文地址:http://www.senocular.com/flash/tutorials/contentdomains/
如果你还没有与复杂的的安全域(security domain)和应用程序域(application domain)问题打过交道,那么你真是个幸运的家伙。当你在加载外部内容(然后他们开始播放)的时候,默认的设置工作的很好,你甚至不知道他们的存在。
但是某些时候你可能需要控制默认设置以外的更多行为和功能,这样你就会遇到前面所说的问题。你也许会困扰于Security.allowDomain和crossdomain.xml文件的区别,又或者你想要深究关于安全性的最佳实践。如果是这样,那么这篇文章就是你所需要的了。
以下的教程将会讨论什么是安全域和应用程序域,以及他们在Flash Player中应该如何使用。
沙箱是用于区分不同的数据和程序执行。沙箱对于安全性尤其重要。如果没有恰当的信任授权,两个位于不同沙箱内的内容应该没有任何交互。Flash Player的安全模型使用称为安全域的沙箱来分离内容。
虽然安全性是沙箱的主要用途,但这并不是唯一使用沙箱的原因。另外一种可能的情形是使用沙箱来避免命名冲突,这种区分代码的沙箱方式在Flash Player中被称为应用域。
安全域在Flash中是顶级的沙箱。安全域链接到内容的来源域名,或者是被加载的内容(如SWF文件)的来源域名。比如在senocular.com下的SWF文件包含一个链接到senocular.com的安全域,而在example.com下的SWF文件则有一个链接到example.com的安全域。不同的安全域使得SWF文件在Flash Player中播放时运行在自身的沙箱下。
注意:在本教程的例子中,你将看到我用统一顶级域名下的不同子域来代表不同域名,这是因为在Flash中,不同子域和不同顶级域一样,都被视为不同的域。示例中代码也被简化过了,在Flash编辑环境下用时间线代码也可以进行测试。
不可执行的内容(非SWF文件),比如图片或者文本文件,也被划分到安全域中,同样与他们所处的域名相关联。实际上,正是域名决定了这些内容是否能够被某个SWF文件加载。更多这方面的内容将在不可执行文件的信任机制章节中进行讨论。
回到SWF上来,安全域划分了数据和可执行代码。如果两个SWF处于不同的安全域下,某个SWF中的数据(比如某个变量)是不可以被其他SWF获取的,当然,代码也不能执行。如果尝试获取其他域中SWF文件的数据将会产生一个安全错误。
下面的代码展示了一个SWF文件企图加载另外一个SWF,并获取其文档类的实例(也就是主时间线)。
http://same.example.com/parent.swf:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, init);
var url:String = "http://diff.example.com/child.swf";
loader.load(new URLRequest(url));
function init(event:Event):void {
trace(loader.content);
// SecurityError: Error #2121: Security sandbox violation:
// Loader.content: http://same.example.com/parent.swf
// cannot access http://diff.example.com/child.swf.
// This may be worked around by calling Security.allowDomain.
}
任意想要获取被加载的SWF文件的内容的尝试,甚至包括trace Loader的content属性。都会引起安全错误,因为他们两者处于不同的安全域内。
安全域的划分也适用于Flash Player所使用的原生ActionScript类。Flash Player在每个安全域中都创建了独立的原生类。举例来说,一个安全域内的XML类与另外一个安全域内的XML类是不相同的,改变其中一个XML的静态属性XML.prettyIndent并不会影响到另一个安全域。
下面这个SWF文件加载了两个子SWF,一个来自自身的域,另一个从另外的域加载。我们改变了主文件的prettyIndent属性,来看看这两个子文件的属性输出。
http://same.example.com/parent.swf:
trace(XML.prettyIndent); // 2
XML.prettyIndent = 5;
trace(XML.prettyIndent); // 5
// Same domain:
var sameLoader:Loader = new Loader();
var sameURL:String = "http://same.example.com/child.swf";
sameLoader.load(new URLRequest(sameURL));
// Different domain:
var diffLoader:Loader = new Loader();
var diffURL:String = "http://diff.example.com/child.swf";
diffLoader.load(new URLRequest(diffURL));
http://same.example.com/child.swf:
trace("same: " + XML.prettyIndent); // same: 5
http://diff.example.com/child.swf:
trace("diff: " + XML.prettyIndent); // diff: 2
可以看到,第二个子文件的属性并没有被改变。这就说明不同的安全域具有不同的原生ActionScript类定义。
尽管安全域只允许相同域下的通讯,但是我们可以通过信任授权来让处于两个不同安全域内的SWF文件进行通讯。通过授权,某个安全域内的文件可以获取另一个域内文件的的数据,或者调用其方法,就像是处于相同的安全域下一样。
在ActionScript中,SWF的信任授权是通过Security.allowDomain(或者类似的Security.allowInsecureDomain)来设置的。在被信任的安全域内的代码可以调用这个方法来授权信任给另一个或者一组在其他安全域内的SWF文件。这种信任是单向的,发起allowDomain的SWF文件不能去访问被授权信任的文件,除非对方也做了信任授权。
在下面的例子中,一个子SWF文件调用allowDomain来允许父SWF的访问:
http://home.example.com/parent.swf:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, init);
var url:String = "http://away.example.com/child.swf";
loader.load(new URLRequest(url));
function init(event:Event):void {
// (子文件执行了allowDomain)
trace(loader.content); <em>// [object DocumentClass]</em>
}
http://away.example.com/child.swf:
Security.allowDomain("home.example.com");
如果没有授信,就像前文说到的,还是会引发安全错误。但是一旦被加载的子SWF调用了allowDomain以后,父SWF文件就可以自由的访问子SWF文件中的内容。要注意的是在这个例子中,由于父SWF文件没有授权away.example.com的信任,所以子SWF仍然无法访问loader的content属性。
信任是非常重要的安全概念,绝对不能掉以轻心。我们经常看见使用通配符的授权:
// 小心哦!
Security.allowDomain("*");
这么做将允许所有SWF文件,不仅仅只是你加载的或是加载你的,可以通过ActionScript来访问你文件中的数据。就算你没有在文件中包含敏感数据,但是如果你在文件中提供了某些方法去获取这种数据,那也有可能被其他SWF调用。使用allowDomain来授权的信任就像给了其他SWF文件相等的权利:你能做什么,我就能做什么。在接下来的合并安全域章节中你将看到这意味着什么。
如果你只是想让SWF之间能够通信,除了信任授权的方法以外我们还可以使用sharedEvents对象来实现,我们将在在非受信的SWF之间通讯章节中讨论。
由于不可执行文件(也就是非SWF文件)不能调用allowDomain代码,所以这类文件的信任机制在Flash Player中有不一样的处理方法。这就是跨域(cross-domain)策略文件派上用场的地方。
跨域策略文件是一个放在网站的根域名下的命名为crossdomain.xml的XML文件。和allowDomain类似,定义了一组可以被Flash Player加载的安全网站域名。一个简单的跨域策略文件的例子如下:
http://example.com/crossdomain.xml:
<?xml version="1.0"?>
<cross-domain-policy>
<site-control permitted-cross-domain-policies="by-content-type"/>
<allow-access-from domain="*.example.com"/>
<allow-access-from domain="www.example-partner.com"/>
</cross-domain-policy>
你可以从Cross-domain policy file specification (adobe.com)获得详细的文件格式信息。
和allowDomain不同的是,跨域策略文件只提供了包含所有文件(通常是一个域下的所有文件)的用法。上面的例子表示允许来自example.com的任意子域或www.example-partner.com的SWF文件加载example.com下的文件。
由于存在allowDomain机制,跨域策略文件通常不用于授权SWF文件的访问。跨域加载SWF的时候不会请求跨域策略文件。只有当要把一个跨域的SWF合并到当前的安全域的时候,才需要提供跨域策略文件。这个主题将在合并安全域中进行讨论。
不管是标准的位于域名根目录下的跨域策略文件还是用Security.loadPolicyfile指定的跨域策略文件,都只有在需要的时候才会被加载:当内容被加载的同时,跨域策略文件也被一起加载进来。
加载完成后,Flash Player分析跨越策略文件并判断该域是否为信任SWF所处的域。如果答案是肯定的话,文件正常加载,就像处于和SWF文件相同的域一样。反之则有可能有两种情况:
如果文件本身就是数据(文本文件,XML文件,二进制数据等等),那么文件就不会被加载。
需要跨域策略文件来加载仅包含数据的文件
如果文件除了数据以外还有其余用途(图像文件,声音文件等),那么文件还是能够被加载到用户可见(可听)的环境中。比如说图像文件,就算没有跨域策略文件,还是可以在Loader对象中显示给用户看。但是类似BitmapData.draw等直接访问图像数据的方法就不能运行。
需要跨域策略文件来获取其他文件的数据的引用
对此可能大家都有点疑惑。实际上用户是可以访问这些数据的,但是SWF文件不行。Flash Player是在保护用户的数据不被潜藏有危险代码的SWF文件获取。用户不需要关心跨域策略文件也能正常的浏览网页内容。
但这并不是说跨域策略文件可以被忽视,类似于下面这种过分纵容的跨域策略文件有很多潜在的危险:
<?xml version="1.0"?>
<cross-domain-policy>
<!-- 小心哦! -->
<allow-access-from domain="*"/>
</cross-domain-policy>
警告:使用通配符(*)允许所有域的访问等同于:用户可能可以接触到的所有处于该域下的数据都有可能被任意SWF文件获取。
客户端的Flash Player运行在当前用户的认证下,这就表示用户的数据可能就是Flash Player的数据。而且Flash Player的数据可能被任意在里面运行的SWF获取。Flash Player默认只允许相同域名下的SWF的安全数据,并限制跨域SWF的运行。如果没有这层限制,SWF可以获取任意当前用户可以获取的数据。
举个例子:某用户使用他的认证来登录网页的邮件客户端收取邮件,然后用户打开了一个包含有恶意程序的SWF的页面。如果没有跨域限制的话,这个SWF可以用他现有的认证偷偷地加载用户的邮件页面。用户可以访问的内网也不例外,只要用户能去的地方,SWF就能去。幸好Flash Player阻止了这种获取数据的行为,除非该域通过跨域策略文件给予SWF授权。
记住一个原则,永远不要对包含敏感数据的域开发跨域授权,即使需要上面的信息来进行用户认证。把SWF可以访问的数据划分到不同的域或者子域下面。
Domain Description Policy filelogin.example.com | Hosts user data | None |
feed.example.com | Hosts public data | Includes:<allow-access-from domain="*" /> |
这将使得敏感数据不可被访问,但是仍然可以对其他域下的SWF文件公开你的其他数据。
如果没有跨域策略文件的信任授权,Flash Player禁止非SWF文件的获取。特别是像文本那样的仅包含数据的文件,甚至不会加载。如果你需要从一个没有跨域授权的域中获取数据,还是有一个变通的办法。
跨域策略文件用于保护数据,特别是保护用户数据,或者说是用户能够接触到的数据。因为Flash Player是运行在客户端的,但是服务器端的代码没有这种限制。服务器完全是另外一台机器,所以用户请求和服务器请求是完全无关的。
由于服务器没有用户的限制,服务器端的代码可以从任意公开的网络服务获取数据。也就是说包含SWF的服务器可以用于访问外部域的数据,然后作为相同域的数据返回给Flash Player。由于处在相同的域下,Flash Player就不需要有跨域策略文件了。
下面的代码演示了一个服务器端的php脚本加载外部数据的例子:
http://home.example.com/loader.swf:
var urlLoader:URLLoader = new URLLoader();
var urlVariables:URLVariables = new URLVariables();
// 服务器端获取外部数据的地址
urlVariables.externalURL = "http://away.example.com/content.txt";
// 服务器端的脚本获取外部数据并传给SWF
var serverPage:String = "http://home.example.com/read.php";
var request:URLRequest = new URLRequest(serverPage);
request.data = urlVariables;
urlLoader.load(request);
这种解决方案也有一定的问题。首先你必须能够在服务器端部署代码,某些小项目也许根本不需要服务器环境。
另一个可能更重要的原因是这种方式加倍了网络流量。首先必须从外部域加载到你自己的域下,然后才被下载到你的SWF客户端。同时这也加重了你的服务器的负载。使用跨域策略文件的话就不会有这种问题。
这可以作为一种解决方案,但最好还是能用跨域策略文件来解决。