按一下以給予評分及指教
Related Articles

在這篇文章中,我們將說明使用 CLR 繫結和載入的最佳作法。

Aarthi Ramamurthy 與 Mark Miller

MSDN Magazine May 2009

...

Read more!

本月我們將檢視 AJAX 應用程式環境下的表單,並查看多種實作功能的方法,如自動存檔、Just-In-Time (JIT) 驗證和提交節流等功能

Dino Esposito

MSDN Magazine 六月 2009

...

Read more!

XML 註解提供簡單又有效的方式來記錄您的程式碼。本文說明如何在 Visual Basic 專案中使用和自訂 XML 註解。

Lisa Feigenbaum

MSDN Magazine May 2009

...

Read more!

非固定式文件在安排文字配置和分頁時提供極大的彈性,但是不支援資料繫結,所以您不能動態變更內容。我們在此建置一個可解決此問題的元件。

Vincent Van Den Berghe

MSDN Magazine 四月 2009

...

Read more!

使用測試導向開發方法和模擬 (Mock) 物件,以角色和責任的角度,設計物件導向程式碼,而不依類別階層來分類物件。

Isaiah Perumalla

MSDN Magazine 六月 2009

...

Read more!

Also by this Author

本月我們將檢視 AJAX 應用程式環境下的表單,並查看多種實作功能的方法,如自動存檔、Just-In-Time (JIT) 驗證和提交節流等功能

Dino Esposito

MSDN Magazine 六月 2009

...

Read more!

在這一期的文章中,作者將提供 BST 模式的增強實作,並和 HTM 解決方案做一比較。

Dino Esposito

MSDN Magazine 7 月 2008

...

Read more!

以 Web 為基礎的 Silverlight 2 應用程式與桌面 WPF 應用程式之間,有很強的相似性。Dino 將在本文中說明如何輕鬆重複使用程式碼。

Dino Esposito

MSDN Magazine October 2008

...

Read more!

本月 Dino Esposito 說明 Silverlight 中的瀏覽器互通性階層,將如何滿足您的 Silverlight / 網頁互動需求。

Dino Esposito

MSDN Magazine 十一月 2008

...

Read more!

這個月 Dino Esposito 將透過 Ajax Control Toolkit 與一些聰明的程式碼撰寫技巧,告訴您如何為 Web 應用程式取得 Windows 樣式的強制回應對話方塊。

Dino Esposito

MSDN Magazine Launch 2008

...

Read more!

Popular Articles

我們將為您介紹來自 Microsoft 模式和實務團隊的「WPF 的複合應用程式指引」,讓您了解建置複合應用程式的益處。

Glenn Block

MSDN Magazine 9 月 2008

...

Read more!

Jeff Prosise 說明何時該使用 UpdatePanel,以及何時該改用 WebMethods 或頁面方法。

Jeff Prosise

MSDN Magazine June 2007

...

Read more!

This article introduces 10 development tools that can increase your productivity, give you a better understanding of .NET, and maybe even change the way that you develop applications. The tools covered include NUnit to write unit tests, Reflector to examine assemblies, FxCop to police your code, Regulator to build regular expressions, NDoc to create code documentation and five more.

James Avery

MSDN Magazine July 2004

...

Read more!

我們將藉這個機會向您介紹一些關於 F# 語言的概念,這套全新的語言包含了功能性與物件導向 .NET 語言的項目。接著,我們會協助您開始撰寫一些簡單的程式。

Ted Neward

MSDN Magazine Launch 2008

...

Read more!

Paul DiLascia

MSDN Magazine August 2002

...

Read more!

技術最前線
ASP.NET 查詢字串的驗證
Dino Esposito

下載程式碼位址: CuttingEdge2007_03.exe (168 KB)
Browse the Code Online
數年來,一般 ASP 開發人員實作網頁驗證的方式,都是在每一頁的頂端插入一些泛型程式碼,來擷取使用者憑證、附加 Cookie 及重新導向。如今所有這些重複的程式碼已由 ASP.NET HTTP 的驗證模組加以省略。因此,ASP.NET 應用程式不必再將每一個受保護的網頁連結至選擇的驗證模組。一切都可以透過 web.config 檔案和一堆外部資源 (例如登入頁面和成員資格資料庫),以宣告的方式完成。
ASP.NET 也引進了其他系統模組和程式設計技巧,使重複的程式碼減至最少,並使一般 Web 應用程式功能的實作合理化。例如,網站導覽、匿名使用者及設定檔現在都已成為內建功能,因此您不必一再撰寫或複製其程式碼。
為了加強安全性,ASP.NET 執行階段也納入很多原始防護機制 -- 這可省去開發人員為了防範一些可能的攻擊而需要交叉檢查所輸入資料的麻煩。當然,這不表示 ASP.NET 應用程式在設計上是安全的,但它確實表示安全性比過去高。不過,要升得更高,仍然必須由開發人員自己動手。
ASP.NET 網頁可以在本身張貼資料,並在 HTTP POST 封包的主體中將輸入參數分組。大部分 ASP.NET 應用程式不會像傳統 ASP 應用程式般地頻繁使用查詢字串來傳遞輸入資料。儘管如此,查詢字串仍然是將外部資料匯入至 ASP.NET 網頁的合法方式。但是這些資料將由誰來驗證?
最新統計資料顯示,跨站台的指令碼處理 (Cross-Site Scripting,XSS) 攻擊愈來愈猖獗,它們是最常見的探索式攻擊。成功的 XSS 攻擊一向肇因於輸入資料未驗證或未正確驗證,通常此資料會透過查詢字串入侵。
從 1.1 版開始,ASP.NET 就會預先處理任何張貼的資料 (表單和查詢字串),以尋找可能是 XSS 攻擊者所運用的可疑字元組合。但此關卡不是徹底解決問題的方法,就像 Michael Howard 在 2006 年 11 月所寫的 MSDN®Magazine 文章<安全習慣:開發更安全程式碼的八大要則>(可於 msdn.microsoft.com/msdnmag/issues/06/11/SecureHabits 取得) 中所述,您必須負起完全責任。如果您的網頁使用查詢字串參數,就必須確定在使用之前已通過適當的驗證。這要如何做?
在本專欄中,我建置了一個 HTTP 模組來讀取 XML 檔案,您只需要在此檔案中以硬式編碼的方式加入查詢字串的預期結構。然後此模組就會以給定的結構描述來驗證所要求網頁的查詢字串。您完全不需要修改網頁上的任何程式碼 (如需防止 XSS 攻擊的詳細資訊,請參閱 Microsoft Anti-Cross Site Scripting Library v1.5 (英文))。

問題
開發人員不能夠無條件接受網頁上的查詢字串輸入。所有的值都必須經過驗證,且查詢字串的格式亦需要仔細檢查。如此的驗證程序包含兩個不同的步驟:靜態驗證 (會檢查必要參數的型別及其是否存在) 和動態驗證 (會驗證指定的值與其餘程式碼的預期值是否一致)。動態驗證會針對每一個網頁進行驗證,且無法委派至外部之無從驗證的網頁元件。相反地,靜態驗證則仰賴通用檢查清單 (必要的參數、型別、長度),不需要執行個體化網頁即可執行。
就像您必須在傳統 ASP 中為每一個受保護網頁包括驗證用的泛型程式碼一樣,您也要在 ASP.NET 的每一個網頁中包括查詢字串的驗證程式碼。ASP.NET 雖已將驗證標準碼移到系統提供的小型 HTTP 模組群組,但並未處理查詢字串。此外,XSS 和 SQL 插入式攻擊的增加,也引來需要交叉檢查可能輸入來源的問題。運用外部元件連結到實作查詢字串參數之嚴格驗證的應用程式,可以有很大的幫助,因為這樣可以確保當查詢字串不符合所宣告的結構描述時,不會執行 ASP.NET 網頁要求。
更重要的是,運用外部元件,網頁原始程式碼就不需要任何變更。您唯一需要做的,是透過組態檔在應用程式中註冊該元件,並新增一個 XML 檔案來說明每一個網頁的查詢字串語法。現在讓我們深入細述此策略。

定義策略
ASP.NET 之所以提供 HTTP 模組這個工具,是要讓您在產生及處理所要求的網頁類別之前,可以將您自己的程式碼插入執行階段管線中。從語法觀點來看,HTTP 模組只是一個實作給定介面的類別。從更廣泛的架構面來看,HTTP 模組好比是與應用程式具有相同存留期的一種觀察者。此模組會觀察處理要求的活動,並註冊以接聽一些特定事件,例如 BeginRequest、EndRequest 或 PostMapRequestHandler。您可以在 System.Web.HttpApplication 類別的文件 (msdn2.microsoft.com/0dbhtdck.aspx) 中找到 ASP.NET 要求的應用程式事件之完整清單。
在安裝 HTTP 模組之後,每當 ASP.NET 執行階段所處理的要求到達引發所觀察事件的階段時,該模組就會開始執行。請注意,ASP.NET 執行階段不一定會處理 ASP.NET 應用程式裝載之所有資源的要求。根據預設,除非 IIS 已設定為允許 ASP.NET 處理靜態資源 (例如階層式樣式表 (CSS) 和 JPG 檔),否則會直接由網頁伺服器提供這些資源,而不會涉及 ASP.NET 應用程式。
我的查詢字串 HTTP 模組會接聽開始要求事件,並根據先前載入的結構描述來驗證查詢字串內容。如果參數的數目和提供的值與預期型別相符,該模組就會讓要求進入下一個階段。否則會終止該要求並傳回適當的 HTTP 狀態碼,或者會擲回 ASP.NET 例外狀況。
我提過的 XML 檔案是儲存查詢字串語法的地方。您不一定要使用 XML 檔案 (如果它是 XML 檔案,其結構描述完全由您決定)。您只需要一個資料來源,以宣告方式來保存網頁查詢字串之預期結構的相關資訊。它可以是簡單的 XML 檔案,也可以是複雜的提供者服務。我在 2006 年 6 月的專欄提供了很好的範例,其中說明如何設計會使用提供者的自訂應用程式服務 (msdn.microsoft.com/msdnmag/issues/06/06/CuttingEdge)。

宣告式查詢字串
[圖 1] 顯示查詢字串 HTTP 模組可辨識的範例 XML 檔案和結構描述。在根節點 <querystring> 之下有許多 <page> 節點,數量會與應用程式中可能處理查詢字串值的頁面數量一樣多。在本專欄所附的程式碼中,[圖 1] 顯示的檔案名稱為 web.querystring。此名稱可以是任意名稱,當然,結構描述也是一樣
<!--
<page url="..." abortOnError="TRUE|false">
  <param name="..." 
         type="Int|Text|Bool" 
         optional="FALSE|true"
         length="number (for Text type only)"
         casesensitive="false|true" />
</page>
-->

<querystring>

  <page url="/source/test.aspx" abortOnError="true">
    <param name="id" type="Int" optional="true" casesensitive="true" />
    <param name="code" type="Text" length="5" optional="false" />
    <param name="detailed" type="Bool" optional="false" />
  </page>

  <page url="/source/Test1.aspx" abortOnError="false">
    <param name="guid" type="Int" />
  </page>

</querystring>

(從安全性的角度而言,主要問題不是網頁會透過查詢字串接收值,而是網頁可能使用那些值。如果網頁的些程式碼會處理透過查詢字串傳送的輸入,那麼身為開發人員,您必須確保輸入安全無虞。因此,XML 檔案中的 <page> 節點,應該僅針對在應用程式中會實際透過查詢字串取用資料的網頁來新增)。
在範例結構描述中,<page> 元素有兩個屬性:url 和 abortOnError。前者會指出網頁的相對 URL,後者則是選用的 Boolean 屬性,以指出萬一輸入錯誤時是否要中止網頁要求。如果您選擇要中止網頁,則使用者會收到 HTTP 錯誤或 ASP.NET 例外狀況,視您在查詢字串中發現無法接受的資料之後決定採取的動作而定。不論您選擇如何處理,都不需要編輯相關 ASP.NET 網頁的程式碼。在識別及產生網頁類別之前,HTTP 模組中就可以適當地使要求終止。
還有另一替代方法。其中 HTTP 模組會讓要求執行,但是會將偵測到的詳細資訊新增至 HTTP 內容中,以通知網頁類別。然後就由網頁負責採取適當的對策,例如顯示特定錯誤網頁。在此情況下,網頁作者必須在應用程式的錯誤處理策略內容中,整合處理任何查詢字串的異常狀況。此方式的缺點是,它必須變更與查詢字串相關之每一個網頁的程式碼 (我稍後會再詳述此問題)。
根據預設,abortOnError 屬性會設為 True,表示查詢字串中的任何異常將中止網頁要求。每一個 <page> 節點底下都會有一份 <param> 節點清單,亦即每一個支援的查詢字串參數都有一份。在範例程式碼中,可使用 [圖 2] 的屬性來定義參數。

屬性 描述
Name 指出查詢參數的名稱。
Type 指出查詢字串參數的型別。適用值如下:Text、Int、Bool。
Optional 布林 (Boolean) 屬性,這會指出此參數是否為選用項目。根據預設,它是設定為 False。
Length 指出 Text 型別之參數的最大長度。
CaseSensitive 布林 (Boolean) 屬性,指出參數名稱是否區分大小寫。根據預設,它是設定為 False,表示可以在查詢字串上指定含有任何大小寫字母組合的參數。
在查詢字串中傳遞的所有值,都會由 ASP.NET 接收為字串。因此,定義在 HttpRequest 物件上的 QueryString 屬性,會是一個 NameValueCollection 物件,其中的索引鍵和值都是字串。不過,字串格式只是純序列化的格式。每一個查詢字串參數當然不只可以代表一個字串,也可以代表布林值或數值,還有像 URL、GUID 和檔名之類的特殊字串子型別。因此,在 web.querystring 檔案中,您可以使用自訂列舉型別 (QueryStringParamTypes) 的值,來指定參數的預期型別:
Friend Enum QueryStringParamTypes As Integer
    Text = 1
    Int = 2
    Bool = 3
End Enum
支援的型別清單可加以延伸,例如可用於新增各種數值型別。Text 型別的參數也可以透過 Length 屬性指定最大長度。例如,可接受查詢字串中有一個 5 個字元長之客戶 ID 的網頁,沒有理由不限制相對的參數長度。此外,可使用 web.querystring 來檢查參數名稱的大小寫,並可指定參數為選用參數。web.querystring 檔案的內容會由查詢字串 HTTP 模組加以剖析,然後轉換成記憶體中的物件。

編寫 QueryString HTTP 模組的程式碼
[圖 3] 顯示 QueryString HTTP 模組的原始程式碼。如前所述,HTTP 模組類別會實作 IhttpModule,其中包含 Init 和 Dispose 方法。在應用程式內容中載入及卸載模組時,會叫用這些方法。在 Init 方法中,HTTP 模組一般會為它想要觀察的應用程式事件註冊接聽項。在此案例中,它會為 BeginRequest 事件註冊一個處理常式。此外,該模組也會處理 web.querystring 檔案,並在記憶體中建立其內容的表示。每一個應用程式只會叫用 Init 方法一次 -- 這會讀取並快取組態檔的內容,因此,要等到重新啟動 Web 應用程式時才會偵測到 web.querystring 檔案的任何變更。因為在實際執行環境中,您不太可能需要輸入 web.querystring 檔案的變更,而不停止及重新啟動應用程式,所以這應該不會是個問題。然而,您也可以延伸 [圖 3] 的程式碼,以使用檔案 watcher 物件來偵測 web.querystring 檔案的變更並即時重新載入它。
Imports System
Imports System.Web
Imports System.IO

Public Class QueryStringModule : Implements IHttpModule

    Private _app As HttpApplication
    Private _queryStringData As Hashtable

    Public Sub Init(ByVal context As System.Web.HttpApplication) _
            Implements System.Web.IHttpModule.Init
        _app = context
        AddHandler _app.BeginRequest, AddressOf OnEnter

        ' Load and cache the XML querystring file
        Dim fileName As String = _
            HttpContext.Current.Server.MapPath("web.querystring")
        _queryStringData = QueryStringHelper.LoadFromFile(fileName)
    End Sub

    Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
    End Sub

    Private Sub OnEnter(ByVal source As Object, ByVal e As EventArgs)
        ' Retrieve the query string data structure for the current page
        Dim currentPage As String = _
            HttpContext.Current.Request.Path.ToLower()
        Dim qsDesc As QueryStringDescriptor = _
            _queryStringData.Item(currentPage)

        ' Validate the query string
        Dim isValid As Boolean
        isValid = QueryStringHelper.Validate( _
           HttpContext.Current.Request.QueryString, qsDesc)

        ' Abort the request if validation fails
        If Not isValid Then
            If qsDesc.AbortOnError Then
                HttpContext.Current.Response.StatusCode = 500
                HttpContext.Current.Response.[End]()
            Else
                ' Add information about the error to Context.Items
                HttpContext.Current.Items( _
                    QueryStringHelper.QueryStringValidationStatus) = _
                        QueryStringHelper.GetErrorCode()
            End If
        Else
            ' Add information for the page to the Context.Items
            HttpContext.Current.Items( _
                QueryStringHelper.QueryStringValidationStatus) = _
                    QueryStringHelper.GetErrorCode()

            ' Add typed values  
            HttpContext.Current.Items( _
                QueryStringHelper.QueryStringValues) = _
                    QueryStringHelper.GetTypedValues( _
                        HttpContext.Current.Request.QueryString, qsDesc)
        End If
    End Sub
End Class

web.querystring 檔案的內容會對應到 QueryStringDescriptor 型別的物件,如 [圖 4] 所示。此描述項會包含網頁的 URL、指出驗證失敗時要執行之動作的旗標,以及所支援的查詢字串參數清單。每一個參數都會透過 QueryStringParamInfo 類別的執行個體加以描述。QueryStringParamCollection 是相關集合類別。它是含有一對 Find 方法的一般泛型集合類別:一個用來確認集合中是否含有指定名稱的參數,一個用來傳回參數描述項執行個體。
Friend Class QueryStringDescriptor
    Public Url As String
    Public AbortOnError As Boolean
    Public Parameters As QueryStringParamCollection
End Class

Friend Class QueryStringParamInfo
    Public Name As String
    Public [Type] As QueryStringParamTypes
    Public Length As Integer
    Public [Optional] As Boolean
    Public CaseSensitive As Boolean
End Class

Friend Class QueryStringParamCollection : Inherits Collection( _
        Of QueryStringParamInfo)

    Public Overloads Function Contains(ByVal name As String) As Boolean
        For i As Integer = 0 To Count - 1
            Dim comparison As StringComparison = _
                StringComparison.OrdinalIgnoreCase
            Dim currentItem As QueryStringParamInfo = Item(i)
            If currentItem.CaseSensitive Then
                comparison = StringComparison.Ordinal
            End If

            If String.Equals(currentItem.Name, name, comparison) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function Find(ByVal name As String) As QueryStringParamInfo
        For i As Integer = 0 To Count - 1
            Dim currentItem As QueryStringParamInfo = Item(i)
            If String.Equals(currentItem.Name, name, _
                    StringComparison.OrdinalIgnoreCase) Then
                Return currentItem
            End If
        Next

        Return Nothing
    End Function
End Class

<Flags()> _
Public Enum QueryStringErrorCodes
    NoError = 0
    TooManyParameters = 1
    InvalidQueryParameter = 2
    MissingRequiredParameter = 4
    InvalidContent = 8
End Enum

查詢字串描述項會快取給定網頁之查詢字串的相關資訊。不過,web.querystring 檔案可以參照多重網頁。因此,web.querystring 參照之所有網頁的所有描述項,在雜湊表中都會使用網頁 URL 做為索引鍵加以分組。下列程式碼片段顯示 HTTP 模組的 BeginRequest 處理常式如何擷取目前所要求網頁的描述項:
Dim currentPage As String
currentPage = HttpContext.Current.Request.Path.ToLower()
Dim qsDesc As QueryStringDescriptor = _
    _queryStringData.Item(currentPage)
查詢字串描述項是網頁查詢字串的正確語法,在記憶體中的表示。下一步是要根據此結構描述來驗證所張貼的查詢字串。

查詢字串的驗證
驗證程序包含三個步驟。首先,該模組會計算張貼之查詢字串的參數數目。如果張貼的查詢字串含有超過預期數目的參數,驗證就會失敗。接下來,該模組會在張貼的查詢字串參數上反覆執行,確保每一個參數符合所宣告之結構描述中的項目。如果發現其他不明參數,驗證就會失敗。最後,該模組會在結構描述中定義的所有參數上反覆執行,確認已指定所有必要參數,且每一個指定的參數都含有適當型別的值。
資料驗證步驟會嘗試將給定參數的值,剖析為其宣告型別。以下是用來驗證數值的程式碼片段:
If paramType = QueryStringParamTypes.Int Then
    Dim result As Integer
    Dim success As Boolean = Int32.TryParse(paramValue, result)
    If Not success Then Return False
End If
依據設計,只會從 True 和 False 之類的字串中剖析布林值。querystring HTTP 模組的驗證子系統也接受 yes 和 no 之類的字串。
最後,會剖析查詢字串的內容並驗證型別,此為要求管線中的第一個步驟。如果一切都沒有問題,就會處理該要求。否則,會立即終止該要求,並傳回適當的 HTTP 狀態碼。請看以下範例:
HttpContext.Current.Response.StatusCode = 500
HttpContext.Current.Response.[End]()
會提供使用者一個網頁,如 [圖 5] 所示。您可能會抱怨這樣無從了解 IIS 錯誤的真正原因,但是 HTTP 狀態碼和一般描述已明白指示錯誤出處 -- 該錯誤發生在要求處理期間的內部伺服器端。如前述 Michael Howard 文章中已說明,在錯誤頁面上應儘量不要洩露資訊,以避免傳出詳細資訊給潛在駭客的風險。因此,HTTP 500 錯誤對於真正發生的問題含糊帶過。反正如先前的程式碼片段所示,HTTP 狀態碼是可以任意設定的。
圖 5 查詢字串發生錯誤的結果 (按影像可放大)

注意事項和替代方案
萬一資料格式有錯誤,您就應該終止要求嗎?或是應該在某處快取驗證結果,然後讓網頁程式碼對使用者做出最後決定?而且,您是否應該在要求存留期的最初就攔截並處理查詢字串呢?讓我們先探討後面這一點。
[圖 6] 列出處理要求時的泛應用程式事件。如果不在要求的最開始處查詢字串,那要在何處檢查呢?緊接在授權之後是一個絕佳位置。如果該要求的進度超出授權階段,應該就一定會叫用到網頁 HTTP 處理常式。

事件 描述
BeginRequest 指出要求處理程序的開始。
AuthenticateRequest
PostAuthenticateRequest
包裝要求的驗證程序。
AuthorizeRequest
PostAuthorizeRequestPostAuthorizeRequest
包裝要求的授權程序。
ResolveRequestCache
PostResolveRequestCache>PostResolveRequestCache
包裝的程序:檢查是否能夠以先前快取之輸出網頁為要求提供服務。
PostMapRequestHandler 指出已找到為要求提供服務的 HTTP 處理常式。
AcquireRequestState
PostAcquireRequestState
包裝的程序:擷取要求的工作階段狀態。
PostRequestHandlerExecute 指出已執行為要求提供服務的 HTTP 處理常式。
ReleaseRequestState
PostReleaseRequestState
包裝的程序:釋放要求的工作階段狀態。
UpdateRequestCache
PostUpdateRequestCache
包裝的程序:檢查是否應快取所要求資源的輸出以供未來重複使用。
EndRequest 指出要求處理程序的結束。
但可以稍後再進行嗎?一般而言,包括 PostAcquireRequestState 在內的事件處理常式都可以運作。使用者程式碼 -- 即網頁作者使用程式碼後置撰寫的程式碼,或是以內嵌在 ASPX 檔案內的方式撰寫的程式碼 -- 只有在 PostAcquireRequestState 事件過去之後才會執行。此外,在引發 global PostAcquireRequestState 事件之前,網頁並無法取用查詢字串。然而,您不應該等這麼久。在授權之後及網頁執行之前檢查查詢字串,可幫您省略其他一些作業 -- 亦即工作階段狀態的擷取及檢查輸出快取。如果您會因為查詢字串有錯誤而刪去網頁,就沒有理由先載入工作階段狀態,尤其如果它是來自跨處理序來源的話,如 SQL Server™。
結論是,只有兩個應用程式事件應該執行查詢字串的檢查:BeginRequest 或 PostAuthorizeRequest。如果需要使用者資訊來處理查詢字串的話,就應該選擇後者 (例如,如果容許某些使用者依據其角色指定特定參數)。在此案例中,您也可以將 roles 屬性新增至 [圖 1] 的結構描述中。在其他情況下,若在 BeginRequest 中設置攔截,即可在管線極早階段刪去網頁,以防止進一步處理。
如果您仍然想要由網頁程式碼去處理錯誤查詢字串,並嘗試正常地降級或回復,則情況將大不相同。就此而言,我想在網頁執行之前的任何事件中處理都可以。我會選擇 PostAcquireRequestState,這是在網頁程式碼執行之前、但在管線中可檢查查詢字串的最後位置。在這個位置,您也有可用的工作階段狀態。雖然我尚未提到這一點,但其內容已不言可喻:在要求內建物件的 QueryString 集合中,一開始就有提供查詢字串資訊。
因此,假設您想要 HTTP 模組檢查查詢字串,然後在管線中將它的發現向下傳遞,直到網頁程式碼為止。您可以採行的方案有數種。在討論它們之前,我應該先提醒您,任何這類方案都會牽連到程式碼,且需要變更含有查詢字串的每一個網頁的原始程式碼。
HTTP 模組與負責處理給定要求的處理常式進行通訊的最簡單方式,就是將資料填入 HttpContext 物件的 Items 集合中。Items 屬性是 HTTP 模組和處理常式用來撰寫及讀取資訊的一個雜湊表。任何儲存在 Items 資料表中的資料,都與要求具有相同的存留期。
HTTP 模組可以在 HttpContext 類別上使用靜態 Current 屬性,來存取目前要求的內容物件,如下所示:
HttpContext.Current.Items("QueryStringStatus") = errorCode
只要 Items 為 System.Collections.Hashtable,索引鍵和值即可為任何 .NET 型別。查詢字串模組會使用公用列舉型別來列出所有可能的錯誤碼:
<Flags()> _
Public Enum QueryStringErrorCodes
    NoError = 0
    TooManyParameters = 1
    InvalidQueryParameter = 2
    MissingRequiredParameter = 4
    InvalidContent = 8
End Enum
進一步描述查詢字串錯誤的這些程式碼組合,會填入雜湊表中照慣例命名的位置。HTTP 模組和網頁必須採用相同命名慣例,網頁才可以擷取及使用此資訊。HTTP 模組會定義一個公用常數來代表位置名稱:
Public Const QueryStringValidationStatus As String = _
       "QueryStringValidationStatus"
網頁可使用下列程式碼,從 HTTP 模組中擷取訊息,並決定如何處理該資訊:
Dim result As QueryStringErrorCodes = _ 
    DirectCast(Context.Items( _
        QueryStringHelper.QueryStringValidationStatus), _
        QueryStringErrorCodes)
假設您還要模組將有效之查詢字串的型別值提供給網頁。請考慮使用下列 URL 並假設查詢字串正確:
http://www.yourserver.com/page.aspx?detailed=true
網頁就應該會併入程式碼以剖析查詢字串值,並將它轉換成布林值。在驗證步驟期間,已在 HTTP 模組中完成這種轉換。只要將這些型別值的雜湊表放在另一個 Items 位置,即可與目標網頁共用這些型別值,就這麼簡單 (如需詳細資訊,請參閱原始程式碼)。
更適切的方式,是將新的唯讀屬性新增至每一個含有查詢字串的頁面上。假設您把它叫做 IsValidQueryString,它看起來會是這個樣子:
Public Property IsValidQueryString As Boolean
   Get 
      Dim result As QueryStringErrorCodes = DirectCast( _
        Context.Items(QueryStringHelper.QueryStringValidationStatus), _
            QueryStringErrorCodes)
      Return (result = QueryStringErrorCodes.NoError)
   End Get
End Property
若要再提升,您可以在基底類別上定義此屬性,並從此類別衍生所有具有查詢字串功能的網頁。

結論
並非所有 ASP.NET 網頁都使用查詢字串。不過,查詢字串可做為網頁的輸入來使用。因此,它有可能成為有安全漏洞之網頁被攻擊的管道。如果網頁需要查詢字串關卡,請準備在所有您要使用查詢字串的網頁上重複撰寫相同的程式碼。
本專欄所呈現的 QueryString 模組則不需要在來源網頁上編寫程式碼,而是根據另存在個別 XML 檔案中的給定結構描述,自動檢查所張貼的查詢字串。這表示不僅對現有程式碼毫無影響,又能對攻擊者提供更多內建防護關卡。但要牢記,這並不能完全杜絕可能發生的問題。

如果您要向 Dino 提出問題或意見,請將郵件寄至 cutting@microsoft.com cutting@microsoft.com.


Dino Esposito是 Solid Quality Learning 的輔導老師,也是 Programming Microsoft ASP.NET 2.0 (Microsoft Press,2005) 一書的作者。他目前居住在義大利,並常在世界各地的產業活動與會議發表演說。若想與 Dino 連絡,請將電子郵件寄至 cutting@microsoft.com 或造訪其部落格 weblogs.asp.net/despos

Page view tracker