最近在與一個IOS應用做接口對接,之前一直都沒有遇到什么很大的問題,但是有一天發現可以通過軟件解析app的url,然后直接通過url的拼接修改接口數據,這一下使得數據的安全性和准確性都降低了,於是就想到了url加密。
然后在網上查了一下url的加密算法,使用比較普遍的還是Base64的加密,但是對於如何實現加密,網上的資料確不多,可能是我搜索的關鍵詞不對。既然沒有現成的參考文件,那么就只能靠自己了。因為所有的Controller都繼承一個基Controller,所以比較自然的想到在基Controller中做一些操作,由於需要在執行具體的Action之前對url中的參數進行解密處理,所以聯想到了做Asp.net項目時使用的IHttpModule接口,不過MVC有個更好的功能,那就是過濾器Filter,mvc總共提供了四種默認的Filter接口,IAuthorizationFilter、IActionFilter、IResultFilter和IExceptionFilter,關於這四種Filter的執行時間和使用方法網絡上有很多,這里就不贅述了。下面就我的摸索過程做一個說明,也供大家參考,如果大家有更好的方法,還望不吝告知。
要想能夠解密Url的參數,首先需要獲取的HttpRequest傳遞過來的參數。首先創建一個Filter,我暫且命名為DecodeUrlFitler,繼承至ActionFilterAttribute,這個類已經繼承了IActionFilter接口,它有四個抽象方法,分別是OnActionExecuted(在action執行完后執行)、OnActionExecuting(在action執行前執行)、OnResultExecuted(在view視圖渲染之后執行)、OnResultExecuting(在view視圖渲染之前執行)。很明顯,我們需要重寫OnActionExecuting方法,在action執行之前,將url中的參數進行解密。
第一步:獲取Url中的查詢參數
獲取查詢參數后,如果你仔細觀察,會發現Base64格式的參數有時是經過UrEncode的,所以為了之后能夠准確的進行Base64的解碼,我們需要將參數進行UrlDecode處理。
public class AppActionFilter : ActionFilterAttribute { public override void OnActionExecuting( ActionExecutingContext filterContext ) { HttpRequestBase bases = (HttpRequestBase) filterContext.HttpContext.Request; string url = bases.RawUrl.ToString().ToLower();
//獲取url中的參數 string queryString = bases.QueryString.ToString();
//對獲取到的參數進行UrlDecode處理
queryString = HttpUtility.UrlDecode(queryString);
} }
獲取參數和處理在博客園現在有很多文章都介紹了,在msdn中查看一下類型的方法,上面的代碼就可以很容易寫出來,比較困難的是如何將解析后的url參數替換之前的參數,然后跳轉到相應的action中,然后將執行的結果返回到客戶端。我在這個問題上摸索了好久,最終找到了比較好的一個方法,下面就來說說我摸到的幾塊石頭。
第二步:url的跳轉
第一塊石頭:使用RedirectResult
一開始想到的是重新拼url,將解析出來的參數拼接成一個完整的url,獲取url的路徑可以使用HttpRequestBase的FilePath屬性獲取到路徑,然后獲取到domain,在加上解密的queryString就可以拼接成一條完整的url了。但是如果你查看瀏覽器的報文,會發現這其實是進行了一個url的重定向,如果這樣的話,我們url加密的目的就沒有實現了,url重定向,會將解密的url傳遞到客戶端,這就讓我們的url暴露了,這完全和我們的設想相反,果斷放棄。
第二塊石頭:使用IHttpHandler
之后查資料時,有提到使用IhttpHandler的ProcessRequest處理web請求的。
filterContext.RequestContext.HttpContext.RewritePath(url);//url為虛擬路徑 IHttpHandler httpHandler = new MvcHttpHandler(); httpHandler.ProcessRequest(System.Web.HttpContext.Current);
這樣也可以達到目的,但是如果你的action參數有非string類型的,那么在執行這個方法時會報錯,雖然你看不到,但是你在Global.asax.cs的Application_Error方法中使用Server.GetLastError().GetBaseException();捕獲異常,你會發現類似xxxxxx方法需要的int類型的參數,但是傳遞過來的是string類型等等。功能雖然實現了,但是看着就是不爽,所以繼續摸索下一個方案。
第三塊石頭:使用ActionParameters修改context的參數
寫代碼就是要要耐心,經過我漫長的摸索,我發現沒有加密的url時,ActionExecutingContext的ActionParameters屬性就是url的查詢參數集合,是一個Dictionary<string,object>的類型;但是如果對url進行加密,ActionParameters的參數集合里面只有key,沒有value,所以我就想,能不能通過修改ActionParameters里面的值,然后在帶調用其父類ActionFilterAttribute的OnActionExecuting方法,話不多說,貼出實現的代碼。
//獲取訪問Action參數的描述,主要是參數的類型和參數名稱 ParameterDescriptor[] pds = filterContext.ActionDescriptor.GetParameters(); 2 3 //重新填充參數 4 string paramName = ""; 5 string paramValue = ""; 6 foreach (string param in parameters) 7 { 8 paramName = param.Split('=')[0]; 9 paramValue = HttpUtility.UrlDecode(param.Split('=')[1]); 10 foreach (ParameterDescriptor pd in pds) 11 { 12 if (paramName == pd.ParameterName) 13 { 14 //判斷參數的類型,如果是整形的數據,那么將參數轉換成整形數據 15 if (pd.ParameterType.Name.ToLower() == "int32" || pd.ParameterType.Name.ToLower() == "nullable`1") 16 { 17 filterContext.ActionParameters.Add(paramName, Convert.ToInt32(paramValue)); 18 } 19 else 20 { 21 filterContext.ActionParameters.Add(paramName, paramValue); 22 } 23 break; 24 } 25 } 26 27 } 28 } 29 base.OnActionExecuting(filterContext);
不過下面和大家分享一下,使用參數替換過程中遇到的問題和值得注意的幾點。
1、在添加參數之前,一定要先使用Clear()方法清楚默認生成的參數,不然重新添加參數時,會出現“字典中已經存在此key的值”;還有一種的方法就是遍歷傳遞過來的參數和ActionParameters的中的參數,替換參數的值。
2、第二點要注意的是參數的類型,參數的類型和名稱可以通過ActionDestriptor方法獲取,如果傳遞的參數類型與Action定義的參數類型不一致,會引發參數類型不一致的異常。
3、最后要注意的可空類型的參數,如果action的參數飽含可空類型的非空類型的參數,當可空參數有值時,那么其余的所有參數都要傳遞,並且賦值。最簡單的辦法就是遍歷ActionDestriptor的參數,將所有的參數都加到ActionParameters中並附上值。
//如果飽含可空參數,那么需要不可空的並且不在請求參數列表中的參數添加到參數列表,否則會報錯 30 foreach (ParameterDescriptor pd in pds) 31 { 32 if (!filterContext.ActionParameters.Keys.Contains(pd.ParameterName)) 33 { 34 if (pd.ParameterType.Name.ToLower() == "nullable`1") 35 { 36 filterContext.ActionParameters.Add(pd.ParameterName, null); 37 } 38 else if (pd.DefaultValue == null) 39 { 40 filterContext.ActionParameters.Add(pd.ParameterName, ""); 41 } 42 else 43 { 44 filterContext.ActionParameters.Add(pd.ParameterName, pd.DefaultValue); 45 } 46 } 47 }
還有一種方法是構建路由表,不過我沒有嘗試過,有興趣的可以試下。
如果大家有更好的方法和建議,歡迎更貼拍磚。