Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2311

杜絕ASP.NET網站JavaScript註解外露

$
0
0

ASP.NET MVC的壓縮打包能有效縮小CSS與JS檔案體積,減少HTTP往返次數,進而提升網站效能。JavaScript經壓縮可讀性雖然已大幅下降,但"保護程式邏輯不外洩"的效果仍然有限,不必過度期望。只是壓縮對我還有另一層重大意義: "JavaScript中的註解會被一併移除!"

我很愛在程式裡寫故事註解,把程式邏輯修正的來龍去脈交待清楚,例如:
//2012-04-01 Bug Fix: VIP級使用者呼叫MehtodA前需呼叫MethodB以校正狀態
//2013-04-01 應客服部要求,錯誤次數上限調整為5
//2014-04-01 TODO: 不支援資料量大於1MB,未來如有超過上限需求,此段需修改

手上維護的系統一多,修改動機甚至更動本身很容易迷失在時間的洪流裡,等某天射茶包查出該處修改是禍源,要不是靠註解喚醒記憶,恐怕會有不小心問候到自己爹娘的風險;或者,急著把Bug修掉還原修改,卻忽略當初調整的理由,就會上演"修好A問題,以前改過的B問題又他X的冒出來"悲劇,少不了挨刮。腦海有幾段鮮明記憶: 某段修改在很久之後造成死傷慘重的大爆炸,靠著程式註解詳載使用者堅持調整的始末,第一時間列為呈堂證供,程式人員才沒被當祭品~ 註解能協助程式碼理解、防止邏輯被誤改,緊急時刻還能用來驅邪防身,是程序員的好朋友! 如果講成這樣還不能讓你寫註解,試試這個: 老闆請了個脾氣暴躁的程序員接手維護你的程式,有流言說那傢伙有重傷害前科...

總之,被註解救過小命,就更愛在程式碼加上滿滿註解,救人又救已,連JavaScript也不例外。但這類註解並不適合外流,其中可能涉及能當成八卦的敏感人事時地物,也可能透露開發者真性情,尤其JavaScript註解會原封不動送到Client端,要上到正式環境前,隱藏或移除才是上策。

ASP.NET MVC的打包壓縮功能啟用後,Client將出現<script src="/bundles/script_pack_name?v=XGaE…" type="text/javascript">及壓縮過內容,理論上使用者無從得知原始JavaScript路徑取得原始碼。但我的資安偏執容不下"看起來夠安全",畢竟原始檔案還是放在網站上,萬一有人得知原始路徑或壓縮功能被意外關閉,JavaScript的原始檔含註解就會經由/scripts/blah.js公諸於世,我決心要社絕這一層風險。

當初為專案客製的Script已集中在/scripts/mycode目錄方便管理,因此針對該目錄統一處理即可。一開始想到的做法是用WebGrease工具在每次部署時將JS檔壓成min.js,再將.js的內容也換成壓縮版,當同檔名的.js及min.js同時存在,ASP.NET MVC打包壓縮機制會直接取用min.js版打包,不致重複壓縮;而.js檔已被換成壓縮版,即使用/scripts/mycode/boo.js也看不到原始碼及註解。但這有個缺點 -- 當迫不得已必須在正式台偵錯時,看到的永遠是壓縮後的版本,無法用原始碼偵錯。

最後,我找到一個更彈性的解法: 使用HttpHandler攔截所有/scripts/mycode/*.js請求,讀取JS檔後即時壓縮再傳到Client端;而少數允許偵錯的內部機器,可事先在web.config中列舉IP,針對這些偵錯用IP,則提供未壓縮版本,魚與熊掌兼得。

在ASP.NET MVC網站加入AppScriptHandler.cs:

using Microsoft.Ajax.Utilities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Hosting;
 
namespace MyWeb.Models
{
publicclass AppScriptsHandler : IHttpHandler
    {
publicbool IsReusable
        {
            get { returnfalse; }
        }
 
staticstring[] debugClients = null;
staticstring[] DebugClients
        {
            get
            {
if (debugClients == null)
                {
                    var list = new List<string>();
string config = 
ConfigurationManager.AppSettings["AppScriptsDebugClients"];
if (!string.IsNullOrEmpty(config))
                    {
foreach (string ip in config.Split(',', ';'))
                        {
//Replace to regular expression pattern
                            list.Add(ip.Replace("*", "[0-9]+").Replace(".", "\\."));
                        }
                    }
                    debugClients = list.ToArray();
                }
return debugClients;
            }
        }
//Check if the ip address matches any pattern in DebugClients
staticbool IsDebugClient(string ip)
        {
foreach (string ipPattern in DebugClients)
            {
if (Regex.IsMatch(ip, ipPattern))
returntrue;
            }
returnfalse;
        }
publicvoid ProcessRequest(HttpContext context)
        {
string jsFile = Path.GetFileName(context.Request.Url.AbsolutePath).ToLower();
string jsPath = Path.Combine(
                   HostingEnvironment.MapPath("~/Scripts/mycode"), jsFile);
            var resp = context.Response;
if (!jsFile.EndsWith(".js") || !File.Exists(jsPath))
            {
                resp.Write("//JavaScript not found: " + jsFile);
            }
else
            {
string js = File.ReadAllText(jsPath);
                resp.ContentType = "text/javascript";
if (jsFile.EndsWith(".min.js") || 
                    IsDebugClient(context.Request.UserHostAddress))
                {
//if already minified or from debug clients
                    resp.Write(js);
                }
else
                {
//return minified result
                    Minifier minifier = new Minifier();
                    resp.Write(minifier.MinifyJavaScript(js));
                }
            }
        }
    }
}

在web.config中,加入appSetting指定可偵錯的來源IP: (支援萬用符號"*",酷吧?)
<add key="AppScriptsDebugClients" value="::1;127.0.0.1;192.168.1.*;172.28.*.*"/>

接著,在web.config要指定將script/mycode/*的存取請求交給AppScriptsHandler處理:
<system.webserver>
    <handlers>
    <add name="scripts" path="scripts/mycode/*" verb="GET" type="MyWeb.Models.AppScriptsHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
    </handlers>
</system.webserver>

就這樣,使用偵錯機器可以看到原始版,其他IP只能看到壓縮版,成功對外隱藏開發者不為人知的一面,又能兼顧線上偵錯的便利性,很棒吧!


【延伸閱讀】


Viewing all articles
Browse latest Browse all 2311

Trending Articles