同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代碼需要針對混淆做出些許修改,測試混淆后的代碼而不是混淆前的也是必須的。