前言
無論是ASP.NET MVC還是Web API框架,在從請求到響應這一過程中對於請求信息的認證以及認證成功過后對於訪問頁面的授權是極其重要的,用兩節來重點來講述這二者,這一節首先講述一下關於這二者的一些基本信息,下一節將通過實戰以及不同的實現方式來加深對這二者深刻的認識,希望此文對你有所收獲。
Identity
Identity代表認證用戶的身份,下面我們來看看此接口的定義
public interface IIdentity { // Properties string AuthenticationType { get; } bool IsAuthenticated { get; } string Name {get; } }
該接口定義了三個只讀屬性, AuthenticationType 代表認證身份所使用的類型, IsAuthenticated 代表是否已經通過認證, Name 代表身份的名稱。對於AuthenticationType認證身份類型,不同的認證身份類型對應不同的Identity,若采用Windows集成認證,則其Identity為WindowsIdentity,反之對於Form表單認證,則其Identity為FormsIdentity,除卻這二者之外,我們還能利用GenericIdentity對象來表示一般意義的Identity。
-
WindowsIdentity
在WindowIdentity對象中的屬性Groups返回Windows賬號所在的用戶組,而屬性IsGuest則用於判斷此賬號是否位於Guest用戶組中,最后還有一個IsSystem屬性很顯然表示該賬號是否是一個系統賬號。在對於匿名登錄中,該對象有一個IsAnonymous來表示該賬號是否是一個匿名賬號。並且其方法中有一個GetAnonymous方法來返回一個匿名對象的WindowsIdentity對象,但是此WindowsIdentity僅僅只是一個空對象,無法確定對應的Windows賬號。
-
FormsIdentity
我們來看看此對象的定義
public class FormsIdentity : ClaimsIdentity { public FormsIdentity(FormsAuthenticationTicket ticket); protected FormsIdentity(FormsIdentity identity); public override string AuthenticationType { get; } public override IEnumerable<Claim> Claims { get; } public override bool IsAuthenticated { get; } public override string Name { get; } public FormsAuthenticationTicket Ticket { get; } public override ClaimsIdentity Clone(); }
一個FormsIdentity對象是通過加密過的認證票據(Authentication Ticket)或者是安全令牌(Security Token)來創建,被加密的內容或者是Cookie或者是請求的URl,下述就是通過FormsIdentity來對Cookie進行加密。
var ticket = new FormsAuthenticationTicket(1, "cookie", DateTime.Now, DateTime.Now.AddMinutes(20), true, "userData", FormsAuthentication.FormsCookiePath); var encriptData = FormsAuthentication.Encrypt(ticket);
-
GenericIdentity
以上兩者都有其對應的Identity類型,如果想自定義認證方式只需繼承該類即可,它表示一般性的安全身份。該類繼承於IIdentity接口。至於如何判斷一個匿名身份只需通過用戶名即可,若用戶名為空則對象的屬性IsAuthenticated為true,否則為false。
Principal
這個對象包含兩個基本的要素即基於用戶的安全身份以及用戶所具有的權限,而授權即所謂的權限都是基於角色而綁定,所以可以將此對象描述為:【身份】+【角色】。
首先我們來看看此接口
public interface IPrincipal { bool IsInRole(string role); IIdentity Identity { get; } }
上述基於IIdentity接口的實現即WindowsIdentity和GenericIdentity,當然也就對應着Principal類型即WindowsPrincipal和GenericPrincipal,除此之外還有RolePrincipal,關於這三者就不再敘述,我們重點來看看下APiController中的IPrincipal屬性。
APiController中User
我們看看此User屬性
public IPrincipal User { get; }
繼續看看此屬性的獲取
public IPrincipal User { get { return Thread.CurrentPrincipal; } }
到這里還是不能看出什么,即使你用VS編譯器查看也不能查看出什么,此時就得看官方的源碼了。如下:
public HttpRequestContext RequestContext { get { return ControllerContext.RequestContext; } set {......} } public IPrincipal User { get { return RequestContext.Principal; } set { RequestContext.Principal = value; } }
到這里我們看出一點眉目了
IPrincipal的屬性User顯然為當前請求的用戶並且與HttpRequestContext中的屬性Principal具有相同的引用。
那么問題來了,HttpRequestContext又是來源於哪里呢?
我們知道寄宿模式有兩種,一者是Web Host,另一者是Self Host,所以根據寄宿模式的不同則請求上下文就不同,我們來看看Web Host中的請求上下文。
- Web Host
internal class WebHostHttpRequestContext : HttpRequestContext { private readonly HttpContextBase _contextBase; private readonly HttpRequestBase _requestBase; private readonly HttpRequestMessage _request; public override IPrincipal Principal { get { return _contextBase.User; } set { _contextBase.User = value; Thread.CurrentPrincipal = value; } } }
從這里我們可以得出一個結論:
Web Host模式下的Principal與當前請求上下文中的User具有相同的引用,與此同時,當我們將屬性Principal進行修改時,則當前線程的Principal也會一同進行修改。
- Self Host
我們看看在此寄宿模式下的對於Principal的實現
internal class SelfHostHttpRequestContext : HttpRequestContext { private readonly RequestContext _requestContext; private readonly HttpRequestMessage _request; public override IPrincipal Principal { get { return Thread.CurrentPrincipal; } set { Thread.CurrentPrincipal = value; } } }
在此模式我們可以得出結論:
Self Host模式下的Principal默認是返回當前線程使用的Principal。
接下來我們來看看認證(Authentication)以及授權(Authorization)。
AuthenticationFilter
AuthenticationFilter是第一個執行過濾器Filter,因為任何發送到服務器請求Action方法首先得認證其身份,而認證成功后的授權即Authorization當然也就在此過濾器之后了,它被MVC5和Web API 2.0所支持。下面用一張圖片來說明這二者在管道中的位置及關系
接下來我們首先來看看第一個過濾器AuthenticationFilter的接口IAuthenticationFilter的定義:
public interface IAuthenticationFilter : IFilter { Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken); Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken); }
該接口定義了兩個方法,一個是 AuthenticateAsync ,它主要認證用戶的憑證。另一個則是 ChallengeAsync ,它主要針對於在認證失敗的情況下,向客戶端發送一個質詢(Chanllenge)。
以上兩者關於認證的方法都分別對應定義在Http協議的RFC2612以及RFC2617中,並且兩者都是基於以下兩點:
-
如果客戶端沒有發送任何憑證到服務器,那么將返回一個401(unauthorized)響應到客戶端,在返回到客戶端的響應中包括一個WWW-Authenticate頭,在這個頭中包含一個或多個質詢,並且每個質詢將指定被服務器識別的認證組合。
-
在從服務器響應返回一個401到客戶端后,客戶端將在認證頭里發送所有的憑證。
下面我們詳細查看每個方法的內容:
AuthenticateAsync
此方法中的參數類型為HttpAuthenticationContext,表示為認證上下文,我們看看此類的實現
public class HttpAuthenticationContext { public HttpAuthenticationContext(HttpActionContext actionContext, IPrincipal principal) { if (actionContext == null) { throw new ArgumentNullException("actionContext"); } ActionContext = actionContext; Principal = principal; } public HttpActionContext ActionContext { get; private set; } public IPrincipal Principal { get; set; } public IHttpActionResult ErrorResult { get; set; } public HttpRequestMessage Request { get { Contract.Assert(ActionContext != null); return ActionContext.Request; } } }
在構造函數中通過Action上下文和認證的用戶的Principal屬性進行初始化,而屬性ErrorResult則返回一個HttpActionResult對象,它是在認證失敗的情況直接將錯誤消息返回給客戶端。我們應該能想到請求到Action方法上的AuthenticationFilter可能不止一個,此時Web API會通過FilterScope進行排序而形成一個AuthenticationFilter管道,緊接着認證上下文會通過當前的Action請求上下文以及通過APiController的User屬性返回的Principal而被創建,最終認證上下文會作為AuthenticationFilter中的AuthenticateAsync方法的參數並進行調用。
當執行為AuthenticateAsync方法被成功執行並返回一個具體的HttpActionResult,此時后續操作將終止,接下來進入第二個方法即【發送認證質詢】階段。
ChallengeAsync
絕大多數認證基本上都是采用【質詢-應答】方式,服務器向端客戶端發出質詢要求來提供憑證,若客戶端在執行AuthenticateAsync方法后,認證未成功,此時服務器端將通過ChallengeAsync方法發送認證質詢。
接下來我們來看看此方法的具體實現
public class HttpAuthenticationChallengeContext { private IHttpActionResult _result; public HttpAuthenticationChallengeContext(HttpActionContext actionContext, IHttpActionResult result) { if (actionContext == null) { throw new ArgumentNullException("actionContext"); } if (result == null) { throw new ArgumentNullException("result"); } ActionContext = actionContext; Result = result; } public HttpActionContext ActionContext { get; private set; } public IHttpActionResult Result { get { return _result; } set { if (value == null) { throw new ArgumentNullException("value"); } _result = value; } } public HttpRequestMessage Request { get { Contract.Assert(ActionContext != null); return ActionContext.Request; } } }
很顯然,當調用AuthenticateAsync方法完成認證工作后,此時ErrorResult將返回一個具體的HttpActionResult,然會將Action上下文以及具體的HttpActionResult傳遞到構造函數中進行初始化HttpAuthenticationChallgeContext,最后依次調用ChallengeAsync方法,當此方法都被執行后,此類中的Result屬性也就返回一個具體的HttpActionResult對象,此對象將創建相應的HttpResponseMessage對象,並回傳到消息處理管道中並作出響應從而發出認證質詢。
總結
(1)在Web API中使用AuthenticationFilter進行認證主要是以下三步
-
Web API會為每個需要被調用Action方法創建所有可能的AuthenticationFilter列表,若有多個則通過FilterScope來進行排序,最終形成AuthenticationFilter管道。
-
Web API將為AuthenticationFilter管道中的每一個過濾器依次調用AuthenticateAsync方法,在此方法中每個AuthenticationFilter將驗證來自客戶端的Http請求憑證,即使在認證過程中觸發到了錯誤,此時進程也不會終止。
-
若認證成功,Web API將調用每個AuthenticationFilter的ChallengeAsync方法,接下來每一個AuthenticationFilter將通過此方法做出質詢響應。
(2)通過上述描述我們用三張示意圖來對照着看
認證方案
我們知道Http協議中的認證方案有兩種,一種是Basic基礎認證,一種是Digest摘要認證
Basic基礎認證
此認證是在客戶端將用戶名和密碼以冒號的形式並用Base64明文編碼的方式進行發送,但是不太安全,因為未被加密,在此基礎上采用Https信息通道加密則是不錯的認證方案。
Digest摘要認證
此認證可謂是Basic基礎認證的升級版,默認是采用MD5加密的方式,在一定程度上算是比較安全的,其執行流程和Basic基礎認證一樣,只是生成的算法不同而已。
未完待續:接下來將通過認證方案手動通過不同的方式來實現認證。。。。。。