FineUI(開源版)v6.0中FState服務器端驗證的實現原理


前言

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 年間,我們看過太多的開源項目轟轟烈烈的來,平平淡淡的去,那些曾經熟悉的身影,曾經陪伴我們的代碼,都已經不復存在。其實很多時候,開源項目不是被新技術淘汰,而是被開源作者所丟棄,不免讓人扼腕嘆息。

每個存在都有存在的價值,時間總會讓之前的東西看起來不再那么新奇好玩,但是還有那么一幫曾經關注的網友,一直在使用的用戶,只有不斷的更新,才不會讓關心你的人失望。

 

 

任何事物的存在價值是無限的!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM