天天看點

認識ASP.NET MVC的5種AuthorizationFilter一、IAuthorizationFilter二、AuthorizeAttribute三、RequireHttpsAttribute 四、ValidateInputAttribute五、ValidateAntiForgeryTokenAttribute六、ChildActionOnlyAttribute

目錄 一、IAuthorizationFilter 二、AuthorizeAttribute 三、RequireHttpsAttribute 四、ValidateInputAttribute 五、ValidateAntiForgeryTokenAttribute 六、ChildActionOnlyAttribute

AuthorizationContext的ActionDescriptor屬性表示描述目前執行Action的ActionDescriptor對象,而Result屬性傳回一個用于在授權階段呈現的ActionResult。AuthorizationFilter的執行是ActionInvoker進行Action執行的第一項工作,因為後續的工作(Model綁定、Model驗證、Action方法執行等)隻有在成功授權的基礎上才會有意義。

ActionInvoker在通過執行AuthorizationFilter之前,會先根據目前的Controller上下文和解析出來的用于描述目前Action的ActionDescriptor,并以此建立一個表示授權上下文的AuthorizationContext對象。然後将此AuthorizationContext對象作為參數,按照Filter對象Order和Scope屬性決定的順序執行所有AuthorizationFilter的OnAuthorization。

在所有的AuthorizationFilter都執行完畢之後,如果指定的AuthorizationContext對象的Result屬性表示得ActionResult不為Null,整個Action的執行将會終止,而ActionInvoker将會直接執行該ActionResult。一般來說,某個AuthorizationFilter在對目前請求實施授權的時候,如果授權失敗它可以通過設定傳入的AuthorizationContext對象的Result屬性響應一個“401,Unauthrized”回複,或者呈現一個錯誤頁面。

很多會将AuthorizeAttribute對方法的授權與PrincipalPermissionAttribute等同起來,實際上不但它們實作授權的機制不一樣(後者是通過代碼通路安全檢驗實作對方法調用的授權),它們的授權政策也一樣。以下面定義的兩個方法為例,應用了PrincipalPermissionAttribute的FooOrAdmin意味着可以被帳号為Foo或者具有Admin角色的使用者通路,而應用了AuthorizeAttribute特性的方法FooAndAdmin方法則隻能被使用者Foo通路,而且該使用者必須具有Admin角色。也就是說PrincipalPermissionAttribute特性對User和Role的授權邏輯是“邏輯或”,而AuthorizeAttribute 采用的則是“邏輯與”。

除此之外,我們可以将多個PrincipalPermissionAttribute和AuthorizeAttribute應用到同一個類型或者方法上。對于前者,如果目前用于通過了任意一個PrincipalPermissionAttribute特性的授權就有權調用目标方法;對于後者來說,意味着需要通過所有AuthorizeAttribute特性的授權在具有了調用目标方法的權限。以如下兩個方法為例,使用者Foo或者Bar可以有權限調用FooOrBar方法,但是沒有任何一個使用者有權調用CannotCall方法(因為一個使用者隻一個使用者名)。

如果目前請求的HTTP方法并不是GET,RequireHttpsAttribute會直接抛出一個InvalidOperationException異常。如上面的代碼片斷所示,針對非HTTPS請求的處理通過調用受保護的方法HandleNonHttpsRequest來完成,如果我們需要不同的處理,可以繼承RequireHttpsAttribute并重寫該方法。

為了避免使用者在請求中包含一些不合法的内容對網站進行惡意攻擊(比如XSS攻擊),我們一般需要對請求的輸入進行驗證。如下面的代碼片斷所示,表示HTTP請求的基類HttpRequestBase具有一個ValidateInput方法用于驗證請求的輸入。實際上這個方法僅僅是在請求上作一下标記而已,在讀取相應的請求輸入時才根據這些表示決定是否需要進行相應的驗證。不過為了便于表達,我們就将針對該ValidateInput方法的調用說成是針對請求輸入的驗證。

所有Controller的基類ControllerBase具有如下一個布爾類型的屬性ValidateRequest表示是否需要對請求輸入進行驗證,在預設情況下該屬性的預設值為True,意味着針對請求輸入的驗證預設情況下是開啟的。

當ActionInvoker在完成了對所有AuthorizationFilter的執行之後,會根據該屬性決定是否會通過調用表示目前請求的HttpRequest對象的ValidateInput方法進行請求輸入的驗證。

為了讓讀者對ValidateInputAttribute這個AuthorizationFilter針對開啟和關閉輸入驗證的作用有一個深刻映像,我們來進行一個簡單的執行個體示範。在通過Visual

Studio的ASP.NET MVC項目模闆建立的空Web應用中我們

定義了如下一個HomeController,包含在該Controller中的兩個Action方法(Action1和Action2)具有一個字元串類型的參數foo,其中Action1上應用了ValidateInputAttribute特性并将參數設定為False。

我們直接運作該程式并在浏覽器中通過輸入相應的位址來通路這兩個Action,并以查詢字元串的形式指定它們的兩個參數。為了檢驗ASP.NET MVC對請求輸入的驗證,我們将表示參數foo的查詢字元串的值設定為為“<script></script>”。如下圖所示,Action1能夠正常地被調用,而Action2在調用過程中抛出異常 ,并提示請求中包含危險的查詢字元串。

<a href="http://images.cnblogs.com/cnblogs_com/artech/201207/201207022238244467.png"></a>

具有如下定義的System.Web.Mvc.ValidateAntiForgeryTokenAttribute用于解決一種叫做“跨站請求僞造(CSRF:Cross-Site Request Forgery)”。這是一種不同于XSS(Cross Site Script)的跨站網絡攻擊,如果說XSS是利用了使用者對網站的信任,而CSRF就是利用了站點對認證使用者的信任。

我們通過一個簡單的例子來對CSRF的原理進行說明。假設我們通過ASP.NET

MVC建構了一個部落格應用,作為部落客的使用者可以發表博文,而一般用于可以對博文發表評論。除此之外,注冊用于可以修改自己的Email位址,相關的操作定義在如下所示的BlogController的Action方法UpdateAddress中。

對于上面定義的UpdateEmailAddress方法,由于應用了AuthorizeAttribute特性,意味着隻有認證的使用者才能調用它來修改自己提供的Email位址。此外,HttpPostAttribute特性應用在該Action方法上,使我們隻能以POST請求的方式調用它,這無形之中也增強了安全系數。但是這個方法提供的Email修改功能真的安全嗎?它真的確定修改後的Email位址真的是登入使用者提供的Email位址嗎?

我們假設BlogController所在的Web應用部署的域名為Foo,那麼Action方法UpdateEmailAddress對應的位址可以表示為http://foo/blog/updateemailaddress。現在一個惡意攻擊者建立如下一個簡單的HTML頁面,該頁面具有一個指向上面這個位址的表單,并且該表單中具有一個名為emailAddress

&lt;input&gt;元素提供屬于供給者自身的Email位址。由于注冊了window的onload事件,該表單會在頁面加載完成之後自動送出。

&lt;/script&gt;

假設攻擊者部署該頁面的位址為http://bar/maliciouspage.html。然後它通過某篇博文中添加一個包含如下連結的評論。作為登入使用者的你點選該連接配接後将會間接地調用定義在BlogController的UpdateEmailAddress方法。由于登入使用者的安全令牌一般以Cookie形式存在,而該Cookie會存在于發送給針對Action方法UpdateEmailAddress的調用請求中,伺服器會認為該請求來自被認證使用者,是以最終造成了你的Email位址被惡意修改而不自知。如果攻擊者具有你的使用者名,它可以通過重置密碼,是新的密碼發送到屬于他自己的電子郵箱中。

這個例子充分說明了CSRF是一種比較隐蔽并且具有很大危害型的網絡攻擊,促成攻擊的原因在于伺服器在針對某個請求執行某個操作的時候并沒有驗證請求的真正來源。對于ASP.NET

MVC來說,如果我們在執行某個Action方法之前能夠确認目前的請求來源的有效性,就能從根本上解決CSRF攻擊,而ValidateAntiForgeryTokenAttribute結合HtmlHelper的AntiForgeryToken方法有效地解決了這個問題。

如上面的代碼片斷所示,HtmlHelper具有三個AntiForgeryToken方法(這裡的方式是HtmlHelper的執行個體方法,不是擴充方法)。當我們在一個View中調用這些方法是,它們會為我們生成一個所謂“防僞令牌(Anti-Forgery Token)”的字元串,并以此生成一個類型為Hidden的&lt;input&gt;元素。除此之外,該方法的調用還會根據這個防僞令牌設定一個Cookie。接下來我們來詳細地來讨論這個過程。

上述的這個防僞令牌通過内部類型為AntiForgeryData的對象生成。如下面的代碼片斷所示,AntiForgeryData具有四個屬性,其核心是通過屬性Value表示的值。屬性UserName和CreationDate表示通路令牌授權的使用者名和建立時間。字元串屬性Salt是為了增強防僞令牌的安全系數,不同的Salt值對應着不同的防僞令牌,不同的防僞令牌在不同的地方被使用以避免供給者對一個防僞令牌的破解而使整個應用受到全面的攻擊。ValidateAntiForgeryTokenAttribute也具有一個同名的屬性。

當AntiForgeryToken被調用的時候,會先根據目前的請求的應用路徑(對應HttpRequest的ApplicationPath屬性)計算出表示防僞令牌Cookie的名稱,該名稱會在通過對應用路徑進行Base64編碼(編碼之前需要進行一些特殊字元的替換工作)生成的字元串前添加“__RequestVerificationToken”字首。

如果目前請求具有一個同名的Cookie,則直接通過對Cookie的值進行反序列化得到一個AntiForgeryData對象。需要注意的是,這裡針對AntiForgeryData進行序列化和反序列化并不是一個簡單地實作運作時對象到字元串之間的轉換,還包含采用MachineKey對AntiForgeryData的四個屬性進行加密/解密的過程。如果這樣的Cookie不存在,HtmlHelper會随機生成一個長度為16的位元組數組,并将對該位元組數組進行Base64編碼後生成的字元串作為值建立一個AntiForgeryData對象。系統目前時間(UTC)作為該AntiForgeryData對象的建立時間,但是該AntiForgeryData對象的UserName和Salt屬性為空。

接下來HtmlHelper會根據之前計算出來的Cookie名稱建立一個)HttpCookie對象,而新建立出來的AntiForgeryData對象被序列化後生成的字元串作為該HttpCookie的值。如果我們在AntiForgeryToken方法調用中設定了表示域和路徑的domain和path參數,它們将會作為該HttpCookie對象的Path和Domain屬性。最後HtmlHelper将HttpCookie對象設定給目前的HTTP響應。

AntiForgeryToken傳回的是一個類型為hidden的&lt;input&gt;元素對應的HTML,該Hidden元素的名稱為“__RequestVerificationToken”(即代碼通路令牌Cookie名稱的字首)。為了生成該Hidden元素的值,HtmlHelper會根據現有的AntiForgeryData對象(從目前請求擷取的或者新建立的)建立一個新的AntiForgeryData對象,兩個對象具有相同的CreationDate和Value屬性,而目前使用者名和指定的Salt參數将會設定給新AntiForgeryData對象的UserName和Salt屬性。

在一個View中我們通過如上的代碼在一個表單中調用HtmlHelper的AntiForgeryToken方法并将一個GUID作為Salt,最終将會生成如下一個名為“__RequestVerificationToken”的Hidden元素。

對于該View的首次通路(或者對應的Cookie不存在),如下所示的名稱為“__RequestVerificationToken_L012Y0FwcDEx”防僞令牌Cookie将會設定,并且是HttpOnly的。

對于一個請求,如果確定請求提供的表單中具有一個名為“__RequestVerificationToken”的Hidden元素,并且該元素的值與對應的防僞令牌的Cookie值相比對,就能夠確定請求并不是由第三方惡意站點發送的,進而防止CSRF攻擊。原因很簡單:由于Cookie值是經過加密的,供給者可以得到整個Cookie的内容,但是不能解密獲得具體的值(AntiForgeryData的Value屬性),是以不可能在提供的表單中也包含一個具有比對值的Hidden元素。針對防僞令牌的驗證就實作在ValidateAntiForgeryTokenAttribute的OnAuthorization方法中。

然後從送出的表單中提取一個名稱為“__RequestVerificationToken”的輸入元素,如果這樣的元素不存在,同樣抛出HttpAntiForgeryException異常;否則直接對具體的值進行反序列化生成一個AntiForgeryData對象。最後ValidateAntiForgeryTokenAttribute對這兩個AntiForgeryData的Value屬性進行比較,以及後者的UserName和Salt屬性與目前使用者名和自身的Salt屬性值進行比較,任何一個不比對都會抛出HttpAntiForgeryException異常。

有的讀者可能會問,AuthorizationFilter如何區分目前的請求是基于子Action的調用,而不是一般的Action調用呢?其實很簡單,當我們在調用HtmlHelper的擴充方法Action或者RenderAction的時候會将目前的View上下文作為“父View上下文”儲存到表示目前路由資訊的RouteData的DataTokens屬性中,對應的Key為“ParentActionViewContext”。如下面的代碼片斷所示,ControllerContext中用于判斷是否為子Action請求的IsChildAction屬性正式通過該路由資訊進行判斷的。

作者:蔣金楠

微信公衆賬号:大内老A

如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識别二維碼)關注個人公衆号(原來公衆帳号蔣金楠的自媒體将會停用)。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

<a href="http://www.cnblogs.com/artech/archive/2012/07/02/AuthorizationFilter.html" target="_blank">原文連結</a>

繼續閱讀