摘要:[ASP.NET WebAPI] WebAPI的使用者驗證及授權
原文出處 by Mike Wasson
在我們建立一個WebAPI站台後,現在我們想要控制每個使用者存取WebAPI的權限。在這篇文章裡將涵蓋使用者驗證及授權兩個目的,同時我們也會學到有哪些與安全性相關的選項可以供我們使用並過濾掉未經授權的使用者。首先我們先來了解一下何謂『驗證』,何謂『授權』?
- 『驗證』 的目的是識別使用者的身分,舉例來說當使用者『愛麗絲』用她的帳號密碼登入時,Server端就透過提供的密碼來辨認出這個使用者就是『愛麗絲』。
- 『授權』 的目的是讓Server端判別這位使用者是否有執行某個功能的權限,舉例來說,使用者『愛麗絲』擁有讀取網頁的權限,但她無建立或修改網頁的權限。
驗證
WebAPI機制裡是透過Host來驗證使用者,就Web-Hosting而言,Host指的就是IIS站台,而IIS站台是透過HTTP模組來驗證使用者的。我們可以透過專案的設定來使用任何IIS或ASP.NET裡內建的驗證模組,或是透過實作HTTP模組來使用自訂的驗證機制。當Host在驗證使用者的時候會產生一個名為主體[Principal]的物件,這個物件實作了IPrincipal介面並且提供執行中的程式確認『使用者是否已驗證』所需的資訊。Host會透過Thread.CurrentPrincipal的屬性將這個Principal物件夾帶在目前的執行序裡。
那執行中的程式怎麼知道使用者已被驗證過呢?
答案是透過Principal的Identity屬性,舉例來說,當使用者的身分為已驗證時Identity.IsAuthenticated的屬性會回傳True,相反地,當使用者透過匿名請求服務時IsAuthenticated會回傳False。
WebAPI如何驗證使用者?
我們可以把驗證使用者的邏輯放進HTTP Message Handlers裡。在這種使用情境下,Message Handler會檢查每個HTTP請求,同時設定與這個請求相關的Principal物件。
我們甚麼時候該用Message Handlers來驗證使用者呢?以下提供一些使用上的考量:
- HTTP模組可以看的到所有流經ASP.NET管線的請求,而Message Handler只看的到走WebAPI路由的請求
- 我們可以針對特定路由掛上Message Handler來提供驗證機制
- IIS與HTTP模組有相依性,而Message Handler與Host無關,所以Message Handler可以用在Web-Hosting或是Self-Hosting
- HTTP模組同時可參與IIS Logging和Auditing等其他特性
- 在請求流經管線時,HTTP模組會在Message Handler前先被執行,所以當我們透過Message Handler處理驗證時,Principal這個物件一直到Message Handler被執行後才會被設定
一般來說,如果我們服務不用支援Self-Hosting的話,透過HTTP模組會是比較好的選擇,不然Message Handler會比較推薦。
該怎麼設定Principal呢?
如果我們的應用程式有使用自訂驗證使用者的邏輯時,我們必須在這兩個地方設定Principal的值:
- Thread.CurrentPrincipal 在.NET裡這是設定執行序所屬Principal的標準方式
- HttpContext.Current.User 這是專門給ASP.NET用的
下面這個簡單的範例教大家怎麼設定Principal。
如果我們是在Web-Hosting的環境下,請記得一定要在這兩個地方設定Principal,不然會發生驗證狀態不一致的情形,但在Self-Hosting的環境底下,HttpContext.Current會是Null,為確保我們的程式在兩種環境下都可以正確執行,在設定Principal前請先檢查HttpContext.Current是否為Null。
授權
授權的處理一樣是在請求進入管線後但在Controller之前,時間點會發生在驗證處理之後,如下圖所示。
- 授權過濾器[Authorization Filter]會比Controller還早被執行,目的就是為了確認該請求是否有使用Controller服務的權限,如果該請求無權限時,授權過濾器就會產生錯誤訊息回傳給請求端,而Controller裡的Action就不會被觸發
- 在Controller的Action裡,我們可以透過ApiController.User這個屬性來取得Principal的值
如何使用[Authorize]這個標籤呢?
WebAPI提供了內建的授權過濾器 - AuthorizeAttribute,在使用這個標籤時,過濾器就會幫我們檢查使用者是否有權限可以使用該服務,當使用者無權限時,過濾器就會回傳HTTP 401 (Unauthorized)的狀態碼給請求端。
在控制器[Controller]的層級裡,我們可以將過濾器設定給全域使用或是給每個動作[Action]使用。
全域層級使用方式:確保每個請求都須經過授權才可以使用控制器提供的服務時,將AuthorizeAttribute過濾器註冊在HttpConfiguration裡面。
控制器層級使用方式:針對特定控制器來設定權限控管時,將Authorize標籤放在控制器命名的上方。
動作層級使用方式:針對特定動作來設定權限控管時,將Authorize標籤放在動作方法命名的上方。
更彈性的使用方式1:當我們想針對控制器做權限控管但又想開匿名存取權限給控制器裡某些動作方法時可以透過允許匿名存取標籤[AllowAnonymous]這樣用。
更彈性的使用方式2:當我們想針對單一使用者或群組使用者做權限控管時可以這樣用。
注意:在WebAPI控制器裡使用AuthorizeAttribute過濾器時記得要帶入System.Web.Http的命名空間,而在MVC控制器裡也有類似的過濾器但是命名空間為System.Web.Mvc,這兩個過濾器是不相容的!
如何使用自訂的授權過濾器?
撰寫自訂的授權過濾器時,我們須繼承以下三種類別的其中一種:
- AuthorizeAttribute:擴展這個類別並使用目前已存在的使用者名稱和角色來執行自訂的授權邏輯
- AuthorizationFilterAttribute:擴展這個類別來同步執行自訂的授權邏輯,但不一定需要目前的使用者名稱和角色
- IAuthorizeFilter:實作這個介面來非同步執行自訂的授權邏輯,舉例來說,我們的授權邏輯裡需要透過呼叫WebService的步驟
在控制器的動作裡提供授權檢查的方式
在某些情況下我們會針對使用者的角色來決定動作裡的行為,比方說我們想針對擁有管理者角色的使用者作額外的處理時,我們可以這樣做: