如何在Visual Studio 2012中发布Web应用程序时自动混淆Javascript
同Java、.NET实现的应用程序类似,Javascript编写的应用程序也面临一个同样的问题:源代码的保护。尽管对大多数Javascript应用公开源代码不算是很严重的问题,但是对于某些开发者来说,特别是HTML5、WebGL和其它纯Javascript实现的项目,知识产权保护是不能忽视的,保护好源代码至少可以增加竞争对手山寨你的应用的成本。通过混淆Javascript代码的方法,可以降低代码的可读性,在一定程度上保护源代码;同时,混淆算法多数都会用非常短的变量名,因此混淆后的代码往往体积更小,网络传输效率和加载运行的效率更高一些。
Javascript代码混淆工具
目前各种开源的或商业的Javascript混淆工具比较多,笔者推荐的是google的。简单的混淆工具通常只做到语法分析的层面,只能重命名函数的参数和函数中var定义的变量。closure compiler是一个用Java编写的Javascript解释执行器,实现上能够完全运行Javascript代码,因此实现了更高级别的混淆,对对象的属性和对象原型中的属性都能够识别和混淆,生成完全混淆的代码。
closure compiler可以下载到本地通过命令行的方式运行,或者通过google提供的WebUI或WebService运行。
本文要在VS发布时应用,因此使用本地命令行的方式运行。
下载closure compiler到本地,进入解压缩后的文件夹,应该包含一个compiler.jar的文件。在该目录下新建一个myjs.js文件,拷一些js代码到该文件。确保本机已安装最新的Java。然后打开控制台,在该目录下运行如下命令:
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js myjs.js >> myjs-compressed.js
在myjs-compressed.js中就可以看到混淆的结果。
这是手工运行compiler.jar的方法。这一步试验成功后,就可以在vs中在发布时运行该命令来混淆发布的代码。
在Visual Studio 2012中发布时运行混淆工具
Visual Studio的发布过程都是高度可配置的,可以在发布的过程中执行自定义的操作。发布过程的配置使用与MSBuild一致的配置语法。
在Web项目上点击发布,到本地文件夹或ftp等都可以,VS会创建一个名为"配置名称.pubxml"的发布配置文件,位于Properties\PublishProfiles文件夹下。打开该文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 您 Web 项目的发布/打包进程将使用此文件。您可以通过编辑此 MSBuild 文件 来自定义该进程的行为。若要了解与此相关的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=208121。 --> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <WebPublishMethod>FileSystem</WebPublishMethod> <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration> <LastUsedPlatform>Any CPU</LastUsedPlatform> <SiteUrlToLaunchAfterPublish /> <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> <ExcludeApp_Data>False</ExcludeApp_Data> <publishUrl>C:\Users\whf\Documents\Visual Studio 2012\Projects\WebApplication2\trunk\WebApplication2\publish</publishUrl> <DeleteExistingFiles>True</DeleteExistingFiles> </PropertyGroup> </Project>
这是选择发布到本地文件夹的一个配置,发布方式对本文无影响。
我们可以在项目已经发布到临时文件夹但是还没有发布到目标文件夹时增加一个Target(Target是MSBuild配置中的一个概念,包含一系列要执行的操作,详见MSDN),在发布临时文件夹下完成Javascript文件的混淆工作。
增加一个名为jsprocess的Javascript混淆Target后的发布配置文件如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 您 Web 项目的发布/打包进程将使用此文件。您可以通过编辑此 MSBuild 文件 来自定义该进程的行为。若要了解与此相关的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=208121。 --> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <WebPublishMethod>FileSystem</WebPublishMethod> <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration> <LastUsedPlatform>Any CPU</LastUsedPlatform> <SiteUrlToLaunchAfterPublish /> <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> <ExcludeApp_Data>False</ExcludeApp_Data> <publishUrl>C:\Users\whf\Documents\Visual Studio 2012\Projects\WebApplication2\trunk\WebApplication2\publish</publishUrl> <DeleteExistingFiles>True</DeleteExistingFiles> </PropertyGroup> <Target Name="jsprocess" AfterTargets="CopyAllFilesToSingleFolderForPackage"> <Exec Command="java -jar "$(MSBuildProjectDirectory)\..\Libs\gogole-closure-compiler.jar" --compilation_level ADVANCED_OPTIMIZATIONS --js $(_PackageTempDir)\myjs.js >> $(_PackageTempDir)\myjs-compressed.js" /> <Delete Files=""$(_PackageTempDir)\myjs.js""> </Delete> <Move SourceFiles="$(_PackageTempDir)\myjs-compressed.js" DestinationFiles="$(_PackageTempDir)\myjs.js"> </Move> <Delete Files=""$(_PackageTempDir)\myjs-compressed.js""> </Delete> </Target> </Project>
这个Target在内置的CopyAllFilesToSingleFolderForPackage Target之后运行,就是所有待发布文件都拷贝到了一个临时文件夹中,等待打包时,对于本地文件夹或ftp的发布方式,打包就是拷贝到目标文件夹。
我们定义的jsprocess执行了3个操作:
1)运行混淆工具 Exec(执行一个程序)
2)删除原始Javascript文件 Delete(删除文件)
3)重命名混淆后的Javascript文件为原始Javascript文件名 Move(移动文件) Delete
在这个示例中我将compiler.jar重命名为google-closure-compiler.jar并添加到了解决方案文件夹中,纳入源代码管理,其它同事获取最新版本后不需要任何配置就可以应用该功能。
通过适当修改上述代码,可以实现混淆所有.js文件。
closure对Javascript代码的要求
基本上,将你需要混淆的代码放在一个闭包中,以字符串索引的形式命名需要暴露的对象的名称和对象的属性的名称(因为closure不会改变Javascript源代码中任何字符串值),就可以用closure实现完美的混淆。
一个简单的示例,在Web项目中myjs.js的源代码如下:
//myjs.js (function (window) { function PrivateClass() { /// <summary> /// 私有类 /// </summary> /// <field name='someProperty1' type='String'>属性1</field> /// <field name='someProperty2' type='Number'>属性2</field> this.someProperty1 = ""; this.someProperty2 = 0; } PrivateClass.prototype.method1 = function () { /// <summary> /// 私有类原型的方法1。 /// </summary> // do some thing } /// <var name='publicObject' type='Object'>对外暴露的公有对象</var> var publicObject = {}; publicObject["publicMethod1"] = function () { /// <summary> /// 公有方法1。 /// </summary> var p = new PrivateClass(); p.someProperty1 = "sdfsdf"; p.someProperty2 = 23; p.method1(); //do some thing return "一些结果"; } window["publicObjectName"] = publicObject; })(window);
发布后的myjs.js内容如下:
(function(b){function c(){this.a="";this.b=0}b.publicObjectName={publicMethod1:function(){var a=new c;a.a="sdfsdf";a.b=23;return"\u4e00\u4e9b\u7ed3\u679c"}}})(window);
在html文件中就可以通过publicObjectName.publicMethod1访问myjs.js暴露的方法。其它诸如PrivateClass、someProperty1、method1等名称都将被混淆为a、b、c之类没有明确意义的名称。
可以注意到,除了重命名之外,closure还对代码进行了很多重构。
结论
通过在发布时实现Javascript的自动混淆,我们可以避免人工介入发布过程,提高发布效率。不管是因为项目性质缘故还是测试要求,项目的发布必然是非常频繁的,完全自动化的发布过程可以节约大量时间。同时,Javascript代码需要针对混淆做出些许修改,测试混淆后的代码而不是混淆前的也是必须的。