Web APi之認證(Authentication)及受權(Authorization)【一】(十二)

前言

不管是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帳號。cookie

  • 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進行加密。  ide

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屬性spa

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基礎認證同樣,只是生成的算法不一樣而已。

未完待續:接下來將經過認證方案手動經過不一樣的方式來實現認證。。。。。。

相關文章
相關標籤/搜索