前言
1. FineUI(開源版)是完整開源,最早發起於 2008-04,下載全部源代碼:http://fineui.codeplex.com/
2. 你可以通過捐贈作者來支持FineUI(開源版)的發展:http://fineui.com/donate/
FineUI的FState與ViewState
早在2013-01 我曾寫過一篇文章,對FState有詳細介紹:http://www.cnblogs.com/sanshi/archive/2013/01/08/2850459.html
現在來簡要回顧一下:
1. ViewState是ASP.NET WebForm的基石,用來在頁面回發過程中維持控件狀態,這樣我們才能在后台方便的使用控件的服務器端屬性。
2. FineUI的AJAX回發過程中,相同的數據會同時存在於ViewState和返回的JavaScript代碼中,造成數據重復浪費!
3. FState機制替換ViewState后,只會在回發數據中保留一份數據,減少了數據的傳輸量。
對於,常見的誤解與糾正:
1. FineUI中不能使用ViewState了。錯!!
FineUI只是實現了一套類似ViewState的機制,但是ViewState本身還是存在的,你依然可以在頁面上調用ViewState對象存儲數據。
2. 不使用ViewState了,FineUI控件不能維持狀態了。錯!!
FState是在AJAX環境中對ViewState的一種改進和提高,目的是為了減少數據傳輸量。你依然可以方便在C#代碼中使用控件屬性
FineUI中的FState可以被惡意篡改
FState用來在頁面回發過程中維持控件的狀態,但是由於FState完全以JavaScript變量的形式暴露出來,很容易被惡意用戶在客戶端進行篡改。
首先來看一個簡單的頁面:
<f:PageManager ID="PageManager1" runat="server" /> <f:DropDownList runat="server" ID="DropDownList1"> <f:ListItem Text="可選項1" Value="Value1" Selected="true" /> <f:ListItem Text="可選項2" Value="Value2" /> <f:ListItem Text="可選項3" Value="Value3" /> </f:DropDownList> <f:Button runat="server" Text="提交" ID="btnSubmit" OnClick="btnSubmit_Click"></f:Button>
后台的按鈕事件:
protected void btnSubmit_Click(object sender, EventArgs e) { Alert.Show("下拉列表選中項:" + DropDownList1.SelectedValue); }
頁面運行效果:
在頁面生成的HTML代碼,我們可以看到 f_state 的身影:
下面我們通過一個例子來講解 FState 的作用,假如用戶在前台對下拉列表的數據進行了重新綁定:
var ddl = F("DropDownList1"); var newdata = [ ["Data1", "數據1", 1], ["Data2", "數據2", 1], ["Data3", "數據3", 1] ]; ddl.store.loadData(newdata); ddl.setValue("Data1");
此時點擊提交按鈕,效果:
之所以后台取不到下拉列表的選中值,是因為后台從FState恢復了下拉列表的項分別是“選項一”,“選項二”和“選項三”。
而對於客戶端重新綁定的新數據源,后台一無所知,因此拿新的選中項值 Data1 去檢索時,自然就找不到對應的項了,所以此時SelectedValue==null
這個邏輯自然是正確的,但是由於 FState 是以JavaScript的形式返回到頁面的,所以惡意用戶自然就可以篡改這個值了:
var ddl = F("DropDownList1"); var newdata = [ ["Data1", "數據1", 1], ["Data2", "數據2", 1], ["Data3", "數據3", 1] ]; ddl.f_state.F_Items = newdata; ddl.store.loadData(newdata); ddl.setValue("Data1");
此時再點擊提交按鈕:
此時服務器已經接受了這個客戶端惡意篡改的值!!這個就不對了。
如果是文本輸入框的值,我們自然是要手工進行服務器端驗證的,不要相信客戶端傳入的任何值,因為都有可能被篡改!
但是如果能默認提供一種內置的驗證機制,讓這種惡意修改FState的行為消失,豈不是更好。FineUI v6.0對此進行了增強。
FineUI v6.0 中默認的FState服務器端驗證
完全相同的例子,在 FineUI v6.0 中,如果通過客戶端修改下拉列表的f_state和內部數據,此時提交按鈕:
這個就是我們的保護機制,保護服務器端輸出的FState信息不會在客戶端被惡意修改。
那么這種保護機制是如何實現的呢?我們從生成的網頁代碼來分析一下:
可以看到,控件除了生成 f_state 屬性,還額外附加了一個 f_state_v 屬性,這個很容易理解為對 f_state 的加密值。
那么在頁面回發時,只需要把這個 f_state_v 的值一塊回發,后台進行解密驗證即可。我們來看下HTTP請求的參數:
這里沒有 f_state_v 的身影,那是因為他隱藏在 F_STATE 變量中的,這個值是 Base64 編碼的,我們解碼后看下:
{ "DropDownList1": [{ "F_Items": [ ["Data1", "\u6570\u636e1", 1], ["Data2", "\u6570\u636e2", 1], ["Data3", "\u6570\u636e3", 1] ], "SelectedValue": "Value1", "SelectedValueArray": ["Value1"] }, "e1ae24"] }
可以看到,這個 f_state_v 的確一起回發到后台了。
這個邏輯其實並不復雜:
1. 頁面初始化時,除了生成控件的 f_state 之外,還額外的生成一個加密后的信息 f_state_v
2. 頁面回發時,后台把這兩個值進行校驗,就知道是否在客戶端被修改了
3. 如果后台控件的屬性發生變化,還重新生成 f_state_v 更新到前台
這里給出后台的主要邏輯代碼,完整源代碼請自行下載:
private static string GetFStateValidation(JObject stateObj) { string fstate = stateObj.ToString(Newtonsoft.Json.Formatting.None); return GetShortMD5HashWithSeed(fstate); } private static bool ValidateFState(JObject stateObj, string validationString) { string fstate = stateObj.ToString(Newtonsoft.Json.Formatting.None); string fstateCode = GetShortMD5HashWithSeed(fstate); if (fstateCode == validationString) { return true; } else { return false; } } private static string GetShortMD5HashWithSeed(string fstate) { string md5HashStr = StringToMD5Hash(fstate + GetFStateValidationSeed()); return md5HashStr.Substring(0, 3) + md5HashStr.Substring(md5HashStr.Length - 3, 3); } private static string StringToMD5Hash(string inputString) { MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); byte[] encryptedBytes = md5.ComputeHash(Encoding.ASCII.GetBytes(inputString)); string a = System.Text.Encoding.Default.GetString(encryptedBytes); StringBuilder sb = new StringBuilder(); for (int i = 0; i < encryptedBytes.Length; i++) { sb.AppendFormat("{0:x2}", encryptedBytes[i]); } return sb.ToString(); } private static string _fstateValidationSeed = String.Empty; private static string GetFStateValidationSeed() { if (String.IsNullOrEmpty(_fstateValidationSeed)) { _fstateValidationSeed = new Guid().ToString(); } return _fstateValidationSeed; }
小結
這篇文章講解了FineUI中的FState取代ViewState的原因,惡意用戶如何在客戶端篡改FState,然后介紹了FineUI v6.0對 FState 的保護機制。
然后我們從生成的頁面HTML入手,簡要分析了FState驗證機制的實現原理。
感興趣的朋友可以自行下載源代碼分析:http://fineui.codeplex.com/
關於開源和堅持
FineUI(開源版)開始於 2008-04,8年多時間內,我們堅持更新了 128 個版本,內部使用的 extjs 從最初的 v2.x,v3.x,到后來的v4.x,FineUI v6.0 使用了最新的extjs v6.2.0。
8 年間,我們看過太多的開源項目轟轟烈烈的來,平平淡淡的去,那些曾經熟悉的身影,曾經陪伴我們的代碼,都已經不復存在。其實很多時候,開源項目不是被新技術淘汰,而是被開源作者所丟棄,不免讓人扼腕嘆息。
每個存在都有存在的價值,時間總會讓之前的東西看起來不再那么新奇好玩,但是還有那么一幫曾經關注的網友,一直在使用的用戶,只有不斷的更新,才不會讓關心你的人失望。
任何事物的存在價值是無限的!