KB-About Event Validation of ASP.NET 2.0
不知你有沒有遇過以下的錯誤?
Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
無效的回傳或回呼引數。已在組態中使用<pages enableEventValidation="true"/> 或在網頁中使用<% Page EnableEventValidation="true" %> 啟用事件驗證。基於安全性理由,這項功能驗證回傳或回呼引數是來自原本呈現它們的伺服器控制項。如果資料為有效並且是必需的,請使用ClientScriptManager.RegisterForEventValidation方法註冊回傳或回呼資料,以進行驗證。
知道它是怎麼冒出來的嗎? 或者你每次都是依著提示將enableEventValidation設成false就當作沒事? 這裡花一點篇幅介紹一下。
首先,這個錯誤源自ASP.NET 2.0所加入的新功能,主要的著眼於防範資料在傳送前被駭客篡改,以強化資安。例如: 你有個下拉選單,決定會員要加到哪個群組,如果駭客戶得知還有個系統管理者群組,偷偷把它加進選項裡,那那那... (不可否認,這類防護挺煩人的,但很不幸地,"用麻煩換資安"一向是鐵律,唉~~~)
我最常看到的情境,多半都是開發者用AJAX或純Javascript方式,依使用者的需求動態變更了下拉選單的內容,接著在送出表單時,噹! Exception~~ 這都肇因於ASPX不認得這些Client-Side動態加入的下拉選項,抱著寧可錯殺一百不可錯放一個的心態,質疑這是遭駭客篡改的結果,用Exception阻擋"入侵"。
我用一個簡單的例子來說明:
<%@ Page Language="C#" AutoEventWireup="true"%>
<html><head><title>Event Validation Test</title></head>
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DropDownList1.Items.Add(new ListItem("1"));
DropDownList1.Items.Add(new ListItem("2"));
}
}
</script>
<body onload="init();">
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="DropDownList1" runat="server">
</asp:DropDownList>
<asp:Button ID="Button1" runat="server" Text="Button" />
</div>
<script type="text/javascript">
function init() {
var sel = document.getElementById("DropDownList1");
sel.options[sel.options.length] = new Option("3");
}
</script>
</form>
</body>
</html>
在上面的例子中,我們在Server-Side為DropDownList1加上選項1, 2,在Client-Side以Javascript加上選項3。實際執行時,如果你選1, 選2,按Button1時一切正常,若選3再按Button1,就會見到前述的Exception。問題出在ASP.NET並不認得我們在Client-Side另行加入的選項3。
除了修改enableEventValidation停用整個網頁的事件檢核防護,錯誤訊息中提到的另一個做法是用ClientScriptManager.RegisterForEventValidation讓ASP.NET認得"3"這個在Client-Side偷生的小孩,寫法如下:
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DropDownList1.Items.Add(new ListItem("1"));
DropDownList1.Items.Add(new ListItem("2"));
}
}
//在Render()加入特別申報邏輯
protected override void Render(HtmlTextWriter writer)
{
//另外為"3"報戶口
ClientScript.RegisterForEventValidation(
DropDownList1.UniqueID,
"3");
//不要忘了呼叫原來的Render()
base.Render(writer);
}
</script>
經過這番修改,選3後再按Button1便不再出錯。不過你應該也會注意到了,Postback後,下拉選單並不會留在3上,畢竟在ASP.NET的認知中,DropDownList1的選項只有1, 2而已。
以上的做法,雖然可以讓"3"就地合法,但如果選項內容是透過AJAX在執行期間呼叫其他Web Page或Web Service取得,在開發ASPX時根本無從得知,則此一解決方式豈不淪為空談? 沒錯! 用EnableEventValidation排除無關資安的情境看來較為最簡便,但每次一設就是整個Web Page,難道不能只針對某些會動態變更的Server Control設定? 不行!
的確,在這個資安議題上,看來沒有簡單有效的折衷之道。有一種做法是開發自訂元件,以不宣告SupportsEventValidation來避開EventValidation,要為此要另外開發元件難稱簡便。我個人實務上慣用的做法是: 既然是AJAX動態管理的下拉選單,何苦執著於要建成DropDownList? 反正前面說過,DropDownList選了動態加入的選項,在Postback時也無法享用自動停在該選項的方便性,還不如直接使用HTML的<SELECT>就好,再設法將選取結果同步到特定HIDDEN INPUT跟Server-Side溝通,這樣就可省去跟資安機制拼命的困擾!
雖然EventValidation會帶來一些困擾,基於資安的考量,還是很鼔勵大家靠它來杜絕駭客動手腳,儘量不要任意停用。