ASPNET-ASPNETCORE 認證

 
話題背景

關於認證個人我的理解是,驗證信息的合法性。在咱們生活當中,好比門禁,你想進入一個有相對安全措施的小區或者大樓,你須要向保安或者門禁系統提供你的身份信息證實,只有肯定你是小區業主,才能夠進來,我這只是打個比方啊,不要糾結。對於咱們計算機的安全領域,認證其實也很是相似,windows系統登錄就是一個很好的例子。今天咱們主要學習的是ASPNET以及ASPNETCORE平臺上面一些主流的認證方式。web

正式話題-認證

我最開始接觸NET平臺的WEB框架是從APSNETWEBFORM開始->ASPNETMVC->ASPNETMVCCORE,下面咱們就從WEBFORM開始吧(包括MVC1.x-4.x)。在MVC5以前,咱們經常使用的認證方式有,Forms、Windows、Passport、None這三種認證方式,嚴格意義上說是三種,None爲不認證,而在這三種認證方式當中,咱們最經常使用的就是Forms表單認證,下面咱們一塊兒來看看Forms表單認證的實現原理。算法

Forms表單認證

我會以我本身的使用方式介紹再到實現原理。整個Forms認證的實現邏輯大概是,說到Forms認證咱們就不得不說ASPNET處理管道,爲何這麼說呢?由於ASPNET的不少基礎功能都是經過相應的HttpModule實現的,好比認證、受權、緩存、Session等等。ASPNET平臺的Forms認證就是基於FormsAuthenticationModule模塊實現,相應的Windows認證也是同樣,由WindowsAuthenticationModule實現。對於Forms認證方式登陸而言。windows

1.匹配用戶名&密碼是否正確。
2.構建FormsAuthenticationTicket對象。api

3.經過FormsAuthentication.Encrypt方法加密Ticker信息。
4.基於加密Ticker信息,構建HttpCookie對象。
5.寫入Response,輸出到客戶端。
以上就是咱們基於Forms表單認證方式的登陸實現邏輯,下面咱們來梳理一下認證的大概實現邏輯,針對每次請求而言。
1.在ASPNET管道生命週期裏,認證模塊FormsAuthenticationModule會接管並讀取Cookie。
2.解密Cookie獲取FormsAuthenticationTicket對象而且驗證是否過時。
3.根據FormsAuthenticationTicket對象構造FormsIdentity對象並設置HttpContext.User。
4.完成認證。
下面咱們一塊兒看看Forms認證的具體實現,我會以我本身開發過程當中使用的方式加以介紹。首先咱們會在web.config文件裏面定義authentication配置節點,以下。緩存

1 <authentication mode="Forms">
2       <forms name="AUTH" loginUrl="~/login" protection="All" timeout="43200" path="/" requireSSL="false" slidingExpiration="true" />
3     </authentication>

 

mode屬性對應了4屬性值,除Forms之外還有上面我提到的三種方式。其餘三種因爲篇幅問題,在這裏不作介紹。這些屬性我相信你們應該都比較熟悉。下面咱們看看關於Forms認證具體的後臺代碼。看代碼。安全

 1 public virtual void SignIn(User user, // 這個user是你校驗合法性以後的這麼一個用戶標識對象
 2 bool createPersistentCookie)
 3         {
 4             var now = DateTime.UtcNow.ToLocalTime();
 5             // 構建Ticker對象
 6             var ticket = new FormsAuthenticationTicket(
 7                 1 ,
 8                user.Username,
 9                 now,
10                 now.Add(_expirationTimeSpan),
11                 createPersistentCookie,
12                 user.Username,
13                 FormsAuthentication.FormsCookiePath);
14             // 加密ticker對象
15             var encryptedTicket = FormsAuthentication.Encrypt(ticket);
16             // 經過加密ticker對象構建HttpCookie對象
17             var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
18             cookie.HttpOnly = true;
19             if (ticket.IsPersistent)
20             {
21                 cookie.Expires = ticket.Expiration;
22             }
23             cookie.Secure = FormsAuthentication.RequireSSL;
24             cookie.Path = FormsAuthentication.FormsCookiePath;
25             if (FormsAuthentication.CookieDomain != null)
26             {
27                 cookie.Domain = FormsAuthentication.CookieDomain;
28             }
29             // 寫入輸出流Response
30             _httpContext.Response.Cookies.Add(cookie);
31         }

 

以上代碼就完成了咱們的Forms認證所需的Cookie信息,可能有些朋友在以往開發WebForms到4.x最經常使用的使用方式是FormsAuthentication.SetAuthCookie(user.UserName, true),其實SetAuthCookie裏面的實現邏輯跟上面實現大同小異,只是我比較喜歡手動建立能夠更多的控制一些輔助信息而已。在以上代碼片斷中,我着重想介紹一下FormsAuthentication.Encrypt(ticket)加密方法,由於它涉及到了Forms認證的安全機制,也好讓各位朋友大概瞭解Forms認證到底安全不安全。FormsAuthentication該對象位於System.Web.Security名稱空間下面,主要做用是安全相關輔助工具類,好比加解密等。
1.在默認狀況下,ASPNETFORMS認證模塊針對Ticker的加密Key是由ASPNET隨機生成,並存儲在本地安全機構LSA中。咱們能夠經過一下代碼片斷驗證這一邏輯。服務器

 1 private CryptographicKey GenerateCryptographicKey(string configAttributeName, string configAttributeValue, int autogenKeyOffset, int autogenKeyCount, string errorResourceString)
 2     {
 3        // 其餘代碼
 4       bool flag1 = false;
 5       bool flag2 = false;
 6       bool flag3 = false;
 7       if (configAttributeValue != null)
 8       {
 9         string str1 = configAttributeValue;
10         char[] chArray = new char[1]{ ',' };
11         foreach (string str2 in str1.Split(chArray))
12         {
13           if (!(str2 == "AutoGenerate"))
14           {
15             if (!(str2 == "IsolateApps"))
16             {
17               if (!(str2 == "IsolateByAppId"))
18               flag3 = true;
19             }
20             else
21               flag2 = true;
22           }
23           else
24             flag1 = true;
25         }
26       }
27       if (flag2)
28         MachineKeyMasterKeyProvider.AddSpecificPurposeString((IList<string>) stringList, "IsolateApps", this.ApplicationName);
29       if (flag3)
30         MachineKeyMasterKeyProvider.AddSpecificPurposeString((IList<string>) stringList, "IsolateByAppId", this.ApplicationId);
31     }

 

以上代碼片斷邏輯也比較簡單,本身體會吧。
2.手動指定machineKey配置節點,該配置節在web.config文件裏面,其中包括可支持的加密算法,加密算法支持DES,3DES,AES等。具體代碼我就不貼了,咱們跟蹤其實現原理意在瞭解Forms認證其安全性。
3.經過以上兩點介紹,我我的認爲Forms認證相對來講很安全。
Forms認證
下面咱們看看Forms的實現原理。
ASPNET的Forms認證發生在ASPNET管道的FormsAuthenticationModule對象裏面,在該對象裏面的Init方法裏面綁定了認證事件OnEnter,具體的認證明現是OnEnter裏面調用的OnAuthenticate方法。咱們來看下代碼。cookie

 1 private void OnAuthenticate(FormsAuthenticationEventArgs e)
 2     {
 3          // 其餘代碼
 4         bool cookielessTicket = false;
 5         // 從請求cookie裏面抽取ticker票據信息
 6         FormsAuthenticationTicket ticketFromCookie = FormsAuthenticationModule.ExtractTicketFromCookie(e.Context, FormsAuthentication.FormsCookieName, out cookielessTicket);
 7         // 過時或者爲null直接返回
 8         if (ticketFromCookie == null || ticketFromCookie.Expired)
 9           return;
10         FormsAuthenticationTicket ticket = ticketFromCookie;
11         // 若是啓用滑動過時,更新過時時間
12         if (FormsAuthentication.SlidingExpiration)
13           ticket = FormsAuthentication.RenewTicketIfOld(ticketFromCookie);
14         e.Context.SetPrincipalNoDemand((IPrincipal) new GenericPrincipal((IIdentity) new FormsIdentity(ticket), new string[0]));
15         if (!cookielessTicket && !ticket.CookiePath.Equals("/"))
16         {
17           cookie = e.Context.Request.Cookies[FormsAuthentication.FormsCookieName];
18           if (cookie != null)
19             cookie.Path = ticket.CookiePath;
20         }
21         if (ticket == ticketFromCookie)
22           return;
23         if (cookielessTicket && ticket.CookiePath != "/" && ticket.CookiePath.Length > 1)
24           ticket = FormsAuthenticationTicket.FromUtc(ticket.Version, ticket.Name, ticket.IssueDateUtc, ticket.ExpirationUtc, ticket.IsPersistent, ticket.UserData, "/");
25         string cookieValue = FormsAuthentication.Encrypt(ticket, !cookielessTicket);
26         
27         if (cookielessTicket)
28         {
29           e.Context.CookielessHelper.SetCookieValue('F', cookieValue);
30           e.Context.Response.Redirect(e.Context.Request.RawUrl);
31         }
32         else
33         {
34           if (cookie != null)
35             cookie = e.Context.Request.Cookies[FormsAuthentication.FormsCookieName];
36           if (cookie == null)
37           {
38             cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue);
39             cookie.Path = ticket.CookiePath;
40           }
41           if (ticket.IsPersistent)
42             cookie.Expires = ticket.Expiration;
43           cookie.Value = cookieValue;
44           cookie.Secure = FormsAuthentication.RequireSSL;
45           cookie.HttpOnly = true;
46           if (FormsAuthentication.CookieDomain != null)
47             cookie.Domain = FormsAuthentication.CookieDomain;
48           cookie.SameSite = FormsAuthentication.CookieSameSite;
49           e.Context.Response.Cookies.Remove(cookie.Name);
50           e.Context.Response.Cookies.Add(cookie);
51         }
52     } 

 

以上代碼片斷反映了Forms認證具體邏輯,邏輯比較簡單,我也大概作了一些註釋,以上就是ASPNET在MVC5.x以前ASPNETForms認證的實現。接下來咱們對ASPNET5.X以前的版本基於Forms認證作個簡單的總結。
1.用戶在未登陸的狀況下,訪問咱們受保護的資源。
2.FormsAuthenticationModule模塊驗證用戶的合法性,主要是生成Identity對象和設置IsAuthenticated屬性。
3.若是未登陸則endrequest階段跳轉到web.config配置的登陸頁或者硬編碼指定的登陸頁。
4.用戶登陸。
5.匹配用戶名&密碼,若是合法,生成ticker票據和cookie並寫入response。
6.訪問受保護的資源(受權部分)。
7.FormsAuthenticationModule模塊驗證用戶的合法性。
8.若是爲以認證用戶IsAuthenticated=true,受權訪問相應的資源。
後續的每次請求也是6,7,8循環。
針對Forms認證就此告一段落,下面咱們接着介紹MVC5的常規認證方式。
MVC5Cookies認證方式
爲何我要把MVC5的認證方式單獨作一個小結講解呢?它有什麼特別之處嗎?沒錯,ASPNETMVC5引入了新的設計理念OWin,我我的的理解是,解耦webserver容器IIS和模塊化。同時NET4.5也引入了ASPNET.Identity,Identity主要是提供幫助咱們管理用戶、角色以及存儲,固然Identity相較Membership強大多了。對於OWin和Identity我在這裏不作詳細介紹,本身能夠去搜一些帖子看或者查看官方文檔。OWin在WebServers與ASPNETWebApplication之間定義了一套標準接口,其官方的開源實現是Katana這個開源項目,咱們今天要介紹的MVC5的認證就是基於Katana這個開源項目的CookieAuthenticationMiddleware中間件實現的,在介紹CookieAuthenticationMiddleware中間件以前,我想簡單羅列一下MVC5的cookies認證(你也能夠認爲是Katana實現的新的Forms認證)和咱們早期使用的Forms認證作個簡單的對比。
相同點:1.基於cookie認證 2.支持滑動過時策略 3.實現令牌保護 4.重定向。
不一樣點:Identity結合Owin實現了聲明認證Claims-based。
以上是我的的一點理解,下面咱們具體看看認證中間件的實現,CookieAuthenticationMiddleware的定義。網絡

 1 public class CookieAuthenticationMiddleware : AuthenticationMiddleware<CookieAuthenticationOptions>
 2     {
 3         // 其餘成員
 4         public CookieAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, CookieAuthenticationOptions options)
 5             : base(next, options)
 6         {
 7         }
 8         // 建立具體的AuthenticationHandler
 9         protected override AuthenticationHandler<CookieAuthenticationOptions> CreateHandler()
10         {
11             return new CookieAuthenticationHandler(_logger);
12         }
13     }

 

CookieAuthenticationMiddleware裏面就一個方法成員,經過CreateHandler方法建立了具體的CookieAuthenticationHandler對象,咱們的認證核心實現就發生在這個Handler裏面。接下來咱們看看CookieAuthenticationHandler對象的定義。session

 1 internal class CookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>
 2     {
 3         // 其餘成員
 4         private const string HeaderNameCacheControl = "Cache-Control";
 5         private const string HeaderNamePragma = "Pragma";
 6         private const string HeaderNameExpires = "Expires";
 7         private const string HeaderValueNoCache = "no-cache";
 8         private const string HeaderValueMinusOne = "-1";
 9         private const string SessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
10 
11         private bool _shouldRenew;
12         private DateTimeOffset _renewIssuedUtc;
13         private DateTimeOffset _renewExpiresUtc;
14         private string _sessionKey;
15         
16         protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
17         
18         protected override async Task ApplyResponseGrantAsync()
19         
20         protected override Task ApplyResponseChallengeAsync()
21     }

 

從CookieAuthenticationHandler對象的定義來看,其實也能看出一二,主要是針對cookie的相關操做,在該對象成員裏面咱們須要瞭解一下其中的三個方法。
1.AuthenticateCoreAsync,代碼我就不貼了,有興趣的朋友能夠本身查看Katana開源項目的源代碼。該方法內部大概實現思路是:從IOWinContext對象獲取cookie,若是對owin不怎麼熟悉的話,這個context你能夠把它理解爲咱們以前熟悉的HttpContext,而後經過解密出來的cookie字符串構造ClaimsIdentity對象並添加到OwinContext對象Request.User,最後返回AuthenticationTicket對象,該對象包裝的就是當前用戶信息以及相關輔助信息。
2.ApplyResponseGrantAsync,設置、更新或者刪除cookie並寫入response。
3.ApplyResponseChallengeAsync,受權失敗,發生重定向。

 1 public class AuthenticationTicket
 2     {
 3         public AuthenticationTicket(ClaimsIdentity identity, AuthenticationProperties properties)
 4         {
 5             Identity = identity;
 6             Properties = properties ?? new AuthenticationProperties();
 7         }
 8         // 用戶信息
 9         public ClaimsIdentity Identity { get; private set; }
10         // 輔助信息,好比會話、過時等
11         public AuthenticationProperties Properties { get; private set; }
12     }

 

下面咱們一塊兒看看在咱們開發過程當中的應用以及內部實現
Startup是Katana開源項目引入的一種新的模塊初始化方式,其實也沒什麼特別的,就是相關中間件的註冊以及一些默認上下文對象的初始化操做。下面咱們具體看代碼,咱們的MVC5新的認證方式在Startup裏面如何註冊的。

 1 public partial class Startup
 2     {
 3         public void ConfigureAuth(IAppBuilder app)
 4         {
 5             // 其餘代碼
 6             app.UseCookieAuthentication(new CookieAuthenticationOptions
 7             {
 8                 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 9                 LoginPath = new PathString("/Account/Login"),
10                 Provider = new CookieAuthenticationProvider
11                 { }
12             });           
13         }
14     }

 

註冊邏輯很簡單,經過IAppBuilder的擴展方法UseCookieAuthentication實現,接下來咱們看看UseCookieAuthentication擴展方法的內部實現。

 1 public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, PipelineStage stage)
 2         {
 3             if (app == null)
 4             {  }
 5             // 註冊
 6             app.Use(typeof(CookieAuthenticationMiddleware), app, options);
 7             // 加入owin管道
 8             app.UseStageMarker(stage);
 9             return app;
10         }

 

整個註冊邏輯就這麼幾行代碼,相關方法都有註釋。最後在程序初始化過程當中經過Build方法完成Owin管道全部中間件的初始化工做。接下來咱們看看具體的登陸實現。

 1 public async Task<ActionResult> Login(LoginModel model,string returnUrl)
 2 {
 3     // 其餘代碼
 4     if (ModelState.IsValid)
 5     {
 6         AppUser user = await UserManager.FindAsync(model.Name, model.Password);
 7         if (user==null)
 8         {
 9             ModelState.AddModelError("","無效的用戶名或密碼");
10         }
11         else
12         {
13             var claimsIdentity =
14                 await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
15             AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, claimsIdentity);
16             return Redirect(returnUrl);
17         }
18     }
19  
20     return View(model);
21 }

 

經過以上代碼片斷就完成了咱們系統的登陸操做,在以上Login方法裏面,咱們須要注意這麼幾個方法。
1.FindAsync主要是經過Identity實現用戶名和密碼的匹配。
2.CreateIdentityAsync主要是建立ClaimsIdentity對象,該對象後續會寫入cookie。
3.SignIn包裝CreateIdentity方法建立的ClaimsIdentity以及ClaimsPrincipal對象,爲cookie寫入Response提供相關認證信息,只有在設置cookie階段纔會寫入response。
接下來咱們針對Katana裏面的cookie認證作個簡單的總結。
1.用戶在未登陸的狀況下,訪問咱們受保護的資源。
2.CookieAuthenticationMiddleware中間件驗證用戶的合法性。
3.用戶登陸。
4.匹配用戶名&密碼,若是合法,包裝相關認證信息。
5.建立\更新cookie寫入response。
6.訪問受保護的資源。
7.CookieAuthenticationMiddleware中間件解密cookie驗證用戶認證信息。
8.若是爲以認證用戶,受權訪問相應的資源。
後續的每次請求也是6,7,8循環。
以上MVC5新的Cookies認證方式就此告一段落,下面咱們接着介紹ASPNET.Identity三方認證。
三方認證
在咱們介紹三方認證以前,咱們不妨先來了解一下什麼是Claim,你們把它翻譯成聲明,我也就這麼跟着叫把。Claim所描述的是一個用戶單一的某個信息,好比用戶名,只有多個Claim組合才能描述一個完整的用戶ClaimsIdentity對象。我的理解這是一種通用的信息存儲結構,一種規範,能夠很方便的基於用戶數據信息驅動認證和受權而且提供獨立服務,各自都不須要關心本身的實現。在咱們傳統的認證windows或者forms認證方式中,每一個系統都有本身認證方式、受權和用戶數據信息,若是是幾年之前,可能沒有什麼問題,可是在現在飛速發展的互聯網時代,就顯的有很大的侷限性、擴展性以及安全性。接下來咱們要介紹的就是MVC5基於ASPNET.Identity結合Katana實現的三方認證,也就是咱們上面說的基於Claims-based實現第三方認證,這裏咱們以google認證爲例,因爲網絡問題這裏咱們以木宛城主大拿的實例代碼作示例,我會結合實例代碼分析內部實現。
首先咱們須要添加google服務認證中間件。

1 app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
2 {
3      // 如下爲客戶端憑據,能夠經過google認證服務註冊
4     ClientId = "",
5     ClientSecret = "",
6 });

 

其二咱們須要設計實現登陸邏輯,一般狀況下咱們在登陸論壇的時候,旁邊可能會有基於QQ登陸或者別的三方認證提供商。

 1 public ActionResult GoogleLogin(string returnUrl)
 2 {    // 建立AuthenticationProperties對象,咱們能夠理解爲認證複製信息字典
 3     var properties = new AuthenticationProperties
 4     {    
 5         RedirectUri = Url.Action("GoogleLoginCallback",
 6         new { returnUrl = returnUrl })
 7     };
 8     // 初始化google認證相關輔助信息
 9     HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");
10     // 返回401
11     return new HttpUnauthorizedResult();
12 }

 

以上代碼比較簡單,我也作了相應的註釋,其邏輯是初始化google認證的一些輔助信息,而後返回401狀態碼,繼而重定向到google登陸頁。下面咱們看看登陸成功以後的代碼邏輯。

 1 public async Task<ActionResult> GoogleLoginCallback(string returnUrl)
 2 {
 3     // 從google認證服務獲取claims
 4     ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();
 5     // 檢查該用戶是否首次登陸系統
 6     AppUser user = await UserManager.FindAsync(loginInfo.Login);
 7     if (user == null)
 8     {
 9         user = new AppUser
10         {
11             Email = loginInfo.Email,
12             UserName = loginInfo.DefaultUserName,
13             City = Cities.Shanghai,
14             Country = Countries.China
15         };
16         // 持久化用戶數據
17         IdentityResult result = await UserManager.CreateAsync(user);
18         // 緩存
19         result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
20     }
21     ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
22         DefaultAuthenticationTypes.ApplicationCookie);
23     ident.AddClaims(loginInfo.ExternalIdentity.Claims);
24     // 建立用戶ClaimsIdentity對象
25     AuthManager.SignIn(new AuthenticationProperties
26     {
27         IsPersistent = false
28     }, ident);
29     return Redirect(returnUrl ?? "/");
30 }

 

以上就是三方認證的實現方式,下面咱們經過Katana源碼看看三方認證的實現原理。經過上面Katana的cookies認證,咱們瞭解到認證中間件的認證邏輯是實如今相應的AuthenticationHandler裏面,咱們一樣以google爲例,去看看內部的實現。下面咱們一塊兒來上面註冊的認證中間件GoogleOAuth2AuthenticationMiddleware的定義。

 1 public class GoogleOAuth2AuthenticationMiddleware : AuthenticationMiddleware<GoogleOAuth2AuthenticationOptions>
 2     {
 3         // 其餘成員
 4         public GoogleOAuth2AuthenticationMiddleware(
 5             OwinMiddleware next,
 6             IAppBuilder app,
 7             GoogleOAuth2AuthenticationOptions options)
 8             : base(next, options);
 9         // 構建認證handler
10         protected override AuthenticationHandler<GoogleOAuth2AuthenticationOptions> CreateHandler()
11         {
12             return new GoogleOAuth2AuthenticationHandler(_httpClient, _logger);
13         }
14         // 構建httpclienthandler
15         private static HttpMessageHandler ResolveHttpMessageHandler(GoogleOAuth2AuthenticationOptions options);
16     }

 

根據以上代碼片斷咱們瞭解到,GoogleOAuth2AuthenticationMiddleware中間件彷佛比咱們常規的cookies認證多了一個方法ResolveHttpMessageHandler,其實這個方法沒有別的套路,就是輔助建立httpclient對象,完成http請求而已,在handler的認證邏輯裏面須要獲取googletoken,就是經過它來獲取的。
第二個方法CreateHandler返回的GoogleOAuth2AuthenticationHandler對象就是咱們接下來要重點討論的對象。

 1 internal class GoogleOAuth2AuthenticationHandler : AuthenticationHandler<GoogleOAuth2AuthenticationOptions>
 2     {
 3         private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token";
 4         private const string UserInfoEndpoint = "https://www.googleapis.com/plus/v1/people/me";
 5         private const string AuthorizeEndpoint = "https://accounts.google.com/o/oauth2/auth";
 6 
 7         private readonly ILogger _logger;
 8         private readonly HttpClient _httpClient;
 9 
10         public GoogleOAuth2AuthenticationHandler(HttpClient httpClient, ILogger logger)
11         {
12             _httpClient = httpClient;
13             _logger = logger;
14         }
15         // 經過httpclient訪問google認證服務器獲取token,根據token數據包裝Claim
16         protected override async Task<AuthenticationTicket> AuthenticateCoreAsync();
17         // 若是未認證,401受權失敗發生重定向
18         protected override Task ApplyResponseChallengeAsync();
19 
20         public override async Task<bool> InvokeAsync()
21         {
22             return await InvokeReplyPathAsync();
23         }
24         // 調用signin,保存用戶信息  
25         private async Task<bool> InvokeReplyPathAsync();
26     }

 

代碼比較長,我把具體實現刪掉了,實現邏輯我註釋到了方法上面,有興趣的朋友能夠本身多看看源碼。以上就是NET平臺上面一些主流的認證方式和實現原理。接下來咱們繼續介紹ASPNETCORE的認證。

ASPNETCORE認證
熟悉微軟web平臺認證受權體系的朋友應該知道,無論是早期的Forms仍是Katana的cookies甚至是我接下來要介紹的ASPNETCORE基於cookies認證,其實總體的設計邏輯大體都差很少,只是具體實現上的區別,尤爲是OWin的設計理念,固然如今咱們幾乎已經模糊了OWin的慨念,可是在ASPNETCORE平臺上處處都有它的縮影。下面咱們一塊兒來看看ASPNETCOREMVC的認證機制。
在這裏,整個認證邏輯我就直接用一張圖展現:

bc263adafb8c7357f96a22a574c6f4d9.png

畫圖工具是網上在線編輯的,畫的很差,別見怪。下面我簡單解釋一下認證受權流程圖,以cookies認證爲例。
1.認證中間件調用CookieAuthenticationHandler實現認證,若是認證成功設置HttpContext.Use對象。
2.在執行controller中的action以前,執行受權filter,若是有設置受權filter特性。
3.若是controller或者action上沒有受權filter,直接執行action,呈現view。
4.若是有定義受權filter特性,受權過濾器再次檢查用戶是否定證,而且合併Claim,由於能夠指定多個認證scheme,認證階段使用的是默認的sheme。
5.認證失敗,受權filter設置context.Result爲Challenge,在後續cookie認證中間件會發生重定向到login頁面。
6.認證成功,受權失敗,受權filter設置context.Result爲Forbid,在後續cookie認證中間件會發生重定向到權限不足頁面。
7.認證、受權都經過,最後顯示view。
以上就是ASPNETCOREMVC認證受權的主要執行邏輯。接下來咱們一塊兒看看,基於COREMVC的cookies認證的應用以及內部實現。
熟悉ASPNETCORE平臺開發的朋友應該知道,基礎功能模塊的配置初始化,通常分爲兩部曲,註冊服務、配置中間件。固然這少不了NETCORE內置DI容器的功勞,咱們將要介紹的認證系統也不例外。下面咱們具體看看認證系統的配置,經過Startup類型配置,關於startup的提供機制能夠看看我上一篇博客,有詳細介紹。
第一部曲服務配置

 1 public static void AddAuthentication(this IServiceCollection services)
 2         {
 3         
 4             // 其餘代碼
 5             var authenticationBuilder = services.AddAuthentication(options =>
 6             {
 7                 options.DefaultChallengeScheme = AuthenticationDefaults.AuthenticationScheme;
 8                 options.DefaultScheme = AuthenticationDefaults.AuthenticationScheme;
 9                 options.DefaultSignInScheme = AuthenticationDefaults.ExternalAuthenticationScheme;
10             })
11             .AddCookie(AuthenticationDefaults.AuthenticationScheme, options =>
12             {
13                 options.Cookie.Name = $"{CookieDefaults.Prefix}{NopCookieDefaults.AuthenticationCookie}";
14                 options.Cookie.HttpOnly = true;
15                 options.LoginPath = AuthenticationDefaults.LoginPath;
16                 options.AccessDeniedPath = AuthenticationDefaults.AccessDeniedPath;
17             })
18             .AddCookie(AuthenticationDefaults.ExternalAuthenticationScheme, options =>
19             {
20                 options.Cookie.Name = $"{CookieDefaults.Prefix}{CookieDefaults.ExternalAuthenticationCookie}";
21                 options.Cookie.HttpOnly = true;
22                 options.LoginPath = AuthenticationDefaults.LoginPath;
23                 options.AccessDeniedPath = AuthenticationDefaults.AccessDeniedPath;
24             });
25         }

 

以上代碼片斷就完成了cookies認證的所需服務註冊。其實際就是註冊cookies認證所需的基礎對象和輔助配置信息到DI容器,以便中間件能夠經過DI容器方便獲取。AddAuthentication擴展方法,主要是註冊認證系統所需基礎對象。AddCookie擴展方法主要是註冊具體cookie認證Handler對象以及經過options模式配置輔助信息。
第二部曲中間件註冊

1 public static void UseAuthentication(this IApplicationBuilder application)
2         {
3             // 其餘代碼
4             application.UseMiddleware<AuthenticationMiddleware>();
5         }

 

認證中間件的註冊就這麼一句代碼,實際就是ASPNETCORE請求管道添加認證中間件,最後經過Build初始化到這個請求管道,後續全部的請求都會經過這個認證中間件的invoke方法處理,而後傳遞下一個中間件,關於中間件的原理也能夠看我上一篇帖子。認證系統的配置咱們已經準備完成,下面咱們看看系統登陸。
登陸

 1   [HttpPost]
 2         public virtual IActionResult Login(LoginModel model, string returnUrl, bool captchaValid)
 3         {
 4         
 5             // 其餘代碼
 6             if (ModelState.IsValid)
 7             {
 8                 var loginResult = _userService.ValidateUser(model.Username, model.Password);
 9                 switch (loginResult)
10                 {
11                     case LoginResults.Successful:
12                         {
13                             var user = _userService.GetUserByUserName(model.Username);
14                             
15                             _authenticationService.SignIn(user, model.RememberMe);
16 
17                             return Redirect(returnUrl);
18                         }
19                 }
20             }
21 
22             return View(model);
23         }

 

以上登陸代碼片斷比較簡單,主要完成兩個動做,1.收集用戶輸入的用戶名&密碼等信息,而後經過咱們系統的存儲介質,校驗用戶名&密碼的合法性。2.登陸到咱們的認證系統,實現咱們核心登陸邏輯是SignIn方法裏面。下面咱們繼續看看SignIn方法的具體實現。

 1 public virtual async void SignIn(User user, bool isPersistent)
 2         {
 3             // 其餘代碼
 4             // 建立身份信息集合
 5             var claims = new List<Claim>();
 6 
 7             if (!string.IsNullOrEmpty(user.Username))
 8                 claims.Add(new Claim(ClaimTypes.Name, user.Username, ClaimValueTypes.String, AuthenticationDefaults.ClaimsIssuer));
 9 
10             if (!string.IsNullOrEmpty(user.Email))
11                 claims.Add(new Claim(ClaimTypes.Email, user.Email, ClaimValueTypes.Email, AuthenticationDefaults.ClaimsIssuer));
12             
13             var userIdentity = new ClaimsIdentity(claims, AuthenticationDefaults.AuthenticationScheme);
14             var userPrincipal = new ClaimsPrincipal(userIdentity);
15             // 輔助信息
16             var authenticationProperties = new AuthenticationProperties
17             {
18                 IsPersistent = isPersistent,
19                 IssuedUtc = DateTime.UtcNow
20             };
21             // 建立cookie ticket,以備寫入response輸出到客戶端
22             await _httpContextAccessor.HttpContext.SignInAsync(AuthenticationDefaults.AuthenticationScheme, userPrincipal, authenticationProperties);
23         }

 

以上代碼片斷就完成了咱們認證系統的登陸。大體邏輯是構建身份聲明信息,調用HttpContext的SignInAsync方法建立ticket,在endrequest階段建立cookie寫入response。以上就是咱們基於ASPNETCORE平臺開發web應用對於認證的真實應用。接下來咱們重點看看平臺的內部實現。
Cookies認證內部實現
咱們仍是從服務註冊開始吧,畢竟它是完成認證系統的基石。咱們把視線轉移到上面的AddAuthentication方法,註冊服務,咱們看看它到底爲咱們的認證系統註冊了哪些基礎服務,看NETCORE源代碼。

1 public static AuthenticationBuilder AddAuthentication(this IServiceCollection services)
2         {
3             // 其餘代碼   
4             services.AddAuthenticationCore();
5             services.AddDataProtection();
6             services.AddWebEncoders();
7             services.TryAddSingleton<ISystemClock, SystemClock>();
8             return new AuthenticationBuilder(services);
9         }

 

從以上代碼片斷了解到,咱們的認證服務註冊是在平臺AddAuthenticationCore方法裏面完成的。咱們一塊兒看看AddAuthenticationCore方法的實現。

1 public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
2         {
3             services.TryAddScoped<IAuthenticationService, AuthenticationService>();
4             services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
5             services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
6             services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
7             return services;
8         }

 

AddAuthenticationCore方法裏面主要註冊了咱們NETCORE認證系統的三個基礎對象,你能夠把它們理解爲黑幫的一個老大兩個堂主,由它們吩咐下面的小弟完成任務,言歸正傳這三個對象也是完成咱們NETCORE平臺認證的三劍客,經過Provider模式實現,下面咱們一個個來介紹,咱們先看看IAuthenticationService接口的定義。

 1 public interface IAuthenticationService
 2     {
 3         Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
 4 
 5         Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
 6 
 7         Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
 8 
 9         Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
10 
11         Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
12     }

 

IAuthenticationService接口定義了5個方法成員,它自己不實現任何認證邏輯,只是爲IAuthenticationSchemeProvider 和 IAuthenticationHandlerProvider這兩個Provider實現了封裝,提供認證服務的統一接口。下面我大概解釋一下這個5個方法在認證服務中的做用。
1.SignInAsync 登陸操做,若是登陸成功,生成加密ticket,用來標識用戶的身份。
2.SignOutAsync 退出登陸,清除Coookie等。
3.AuthenticateAsync 解密cookie,獲取ticket並驗證,最後返回一個 AuthenticateResult 對象,表示用戶的身份。
4.ChallengeAsync 未認證,返回 401 狀態碼。
5.ForbidAsync 權限不足,返回 403 狀態碼。
下面咱們一塊兒看看它的惟一默認實現類AuthenticationService。

 1 public class AuthenticationService : IAuthenticationService
 2     {
 3     
 4         // 其餘成員
 5         public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform);
 6 
 7         public IAuthenticationSchemeProvider Schemes { get; }
 8 
 9         public IAuthenticationHandlerProvider Handlers { get; }
10 
11         public IClaimsTransformation Transform { get; }
12 
13         public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
14 
15         
16         public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
17         {
18             if (scheme == null)
19             {
20                 var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
21                 scheme = defaultChallengeScheme?.Name;
22                 if (scheme == null)
23                 {
24                     throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found.");
25                 }
26             }
27 
28             var handler = await Handlers.GetHandlerAsync(context, scheme);
29             if (handler == null)
30             {
31                 throw await CreateMissingHandlerException(scheme);
32             }
33 
34             await handler.ChallengeAsync(properties);
35         }
36 
37         public virtual async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
38 
39         public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
40 
41         public virtual async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
42     }

 

代碼比較多,我刪掉了大部分,其實現邏輯都差很少。咱們以ChallengeAsync方法爲例,先獲取相應的scheme,而後獲取對應的Handler,最後執行Handler的同名方法。也就說明,真正的認證邏輯是在Handler裏面完成的。從AuthenticationService的定義瞭解到,AuthenticationService的建立是基於Handlers和schemes建立的,下面咱們看看認證的第二個基礎對象IAuthenticationSchemeProvider。

 1 public interface IAuthenticationSchemeProvider
 2     {
 3         Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
 4 
 5         Task<AuthenticationScheme> GetSchemeAsync(string name);
 6 
 7         Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();
 8 
 9         Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync();
10 
11         Task<AuthenticationScheme> GetDefaultForbidSchemeAsync();
12 
13         Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();
14 
15         Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();
16 
17         void AddScheme(AuthenticationScheme scheme);
18 
19         void RemoveScheme(string name);
20 
21         Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
22     }

 

scheme其實際就是提供認證方案標識,咱們知道,NETCORE的認證系統所支持的認證方案很是豐富,好比openid、bearer、cookie等等。下面咱們一塊兒看看它的默認實現AuthenticationSchemeProvider對象。

 1 public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
 2     {
 3         public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
 4             : this(options, new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal))
 5         {
 6         }
 7 
 8         protected AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes)
 9         {
10             _options = options.Value;
11 
12             _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
13             _requestHandlers = new List<AuthenticationScheme>();
14 
15             foreach (var builder in _options.Schemes)
16             {
17                 var scheme = builder.Build();
18                 AddScheme(scheme);
19             }
20         }
21 
22         private readonly AuthenticationOptions _options;
23         private readonly object _lock = new object();
24         private readonly IDictionary<string, AuthenticationScheme> _schemes;
25         private readonly List<AuthenticationScheme> _requestHandlers;
26         private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
27         private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();
28 
29         private Task<AuthenticationScheme> GetDefaultSchemeAsync()
30             => _options.DefaultScheme != null
31             ? GetSchemeAsync(_options.DefaultScheme)
32             : Task.FromResult<AuthenticationScheme>(null);
33 
34         public virtual Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
35             => _options.DefaultAuthenticateScheme != null
36             ? GetSchemeAsync(_options.DefaultAuthenticateScheme)
37             : GetDefaultSchemeAsync();
38 
39         public virtual Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
40             => _options.DefaultChallengeScheme != null
41             ? GetSchemeAsync(_options.DefaultChallengeScheme)
42             : GetDefaultSchemeAsync();
43 
44         public virtual Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
45             => _options.DefaultForbidScheme != null
46             ? GetSchemeAsync(_options.DefaultForbidScheme)
47             : GetDefaultChallengeSchemeAsync();
48 
49         public virtual Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
50             => _options.DefaultSignInScheme != null
51             ? GetSchemeAsync(_options.DefaultSignInScheme)
52             : GetDefaultSchemeAsync();
53 
54          public virtual Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
55             => _options.DefaultSignOutScheme != null
56             ? GetSchemeAsync(_options.DefaultSignOutScheme)
57             : GetDefaultSignInSchemeAsync();
58 
59          public virtual Task<AuthenticationScheme> GetSchemeAsync(string name)
60             => Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);
61 
62          public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
63             => Task.FromResult(_requestHandlersCopy);
64 
65          public virtual void AddScheme(AuthenticationScheme scheme)
66         {
67             if (_schemes.ContainsKey(scheme.Name))
68             {
69                 throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
70             }
71             lock (_lock)
72             {
73                 if (_schemes.ContainsKey(scheme.Name))
74                 {
75                     throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
76                 }
77                 if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
78                 {
79                     _requestHandlers.Add(scheme);
80                     _requestHandlersCopy = _requestHandlers.ToArray();
81                 }
82                 _schemes[scheme.Name] = scheme;
83                 _schemesCopy = _schemes.Values.ToArray();
84             }
85         }
86 
87          public virtual void RemoveScheme(string name);
88 
89         public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
90             => Task.FromResult(_schemesCopy);
91     }

 

從AuthenticationSchemeProvider的默認實現來看,它主要是提供scheme管理。從AuthenticationSchemeProvider構造器的定義來看,它的初始化是由咱們註冊服務時所提供的options配置對象提供,其最終初始化體如今AddScheme方法上,也就是對全部註冊的scheme添加集合,全部scheme最終體現爲一個AuthenticationScheme對象,下面咱們看看它的定義。

 1 public class AuthenticationScheme
 2     {
 3         public AuthenticationScheme(string name, string displayName, Type handlerType)
 4         {
 5             // 其餘代碼
 6             if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
 7             {
 8                 throw new ArgumentException("handlerType must implement 
 9 IAuthenticationHandler.");
10             }
11 
12             Name = name;
13             HandlerType = handlerType;
14             DisplayName = displayName;
15         }
16 
17         public string Name { get; }
18 
19         public string DisplayName { get; }
20 
21         public Type HandlerType { get; }
22     }

 

每個scheme裏面都包含了對應的Handler,同時派生自IAuthenticationHandler。這個handler就是後續真正處理咱們的認證明現。下面咱們一塊兒看看認證基石的第三個對象IAuthenticationHandlerProvider的定義。

1 public interface IAuthenticationHandlerProvider
2     {
3         Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme);
4     }

 

這個接口的定義很簡單,就一個成員,GetHandlerAsync方法,顧名思義就是獲取authenticationScheme對應的Handler,咱們看看IAuthenticationHandlerProvider的默認實現。

 1 public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
 2     {
 3         public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
 4         {
 5             Schemes = schemes;
 6         }
 7 
 8         public IAuthenticationSchemeProvider Schemes { get; }
 9 
10         private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
11 
12         public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
13         {
14             if (_handlerMap.ContainsKey(authenticationScheme))
15             {
16                 return _handlerMap[authenticationScheme];
17             }
18 
19             var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
20             if (scheme == null)
21             {
22                 return null;
23             }
24             var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
25                 ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
26                 as IAuthenticationHandler;
27             if (handler != null)
28             {
29                 await handler.InitializeAsync(scheme, context);
30                 _handlerMap[authenticationScheme] = handler;
31             }
32             return handler;
33         }
34     }

 

GetHandlerAsync方法的實現邏輯也比較簡單,首先經過_handlerMap字典根據scheme名稱獲取,通常首次獲取,都是null。而後經過schemeprovide獲取對應的scheme,經過上面分析咱們知道,scheme體現爲一個AuthenticationScheme對象,裏面包含了handlertype。最後建立這個handler,建立handler有兩種狀況,第一種從DI容器獲取,第二種狀況反射建立,最終返回的是有以下定義的IAuthenticationHandler接口。

 1 public interface IAuthenticationHandler
 2     {
 3         Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
 4 
 5         Task<AuthenticateResult> AuthenticateAsync();
 6 
 7         Task ChallengeAsync(AuthenticationProperties properties);
 8 
 9         Task ForbidAsync(AuthenticationProperties properties);
10     }

 

該接口就是實打實幹實事的,咱們的認證邏輯就是經過該handler實現的。AuthenticateAsync方法就是咱們的認證入口,其返回類型是一個AuthenticateResult類型,也就是咱們的認證結果,接下來咱們看看它的定義。

 1 public class AuthenticateResult
 2     {
 3         protected AuthenticateResult() { }
 4 
 5         public bool Succeeded => Ticket != null;
 6 
 7         public AuthenticationTicket Ticket { get; protected set; }
 8 
 9         public ClaimsPrincipal Principal => Ticket?.Principal;
10 
11         public AuthenticationProperties Properties { get; protected set; }
12 
13         public Exception Failure { get; protected set; }
14 
15         public bool None { get; protected set; }
16 
17         public static AuthenticateResult Success(AuthenticationTicket ticket)
18         {
19             if (ticket == null)
20             {
21                 throw new ArgumentNullException(nameof(ticket));
22             }
23             return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
24         }
25 
26         public static AuthenticateResult NoResult()
27         {
28             return new AuthenticateResult() { None = true };
29         }
30 
31         public static AuthenticateResult Fail(Exception failure)
32         {
33             return new AuthenticateResult() { Failure = failure };
34         }
35 
36         public static AuthenticateResult Fail(Exception failure, AuthenticationProperties properties)
37         {
38             return new AuthenticateResult() { Failure = failure, Properties = properties };
39         }
40 
41         public static AuthenticateResult Fail(string failureMessage)
42             => Fail(new Exception(failureMessage));
43 
44         public static AuthenticateResult Fail(string failureMessage, AuthenticationProperties properties)
45             => Fail(new Exception(failureMessage), properties);
46     }

 

如上代碼,AuthenticateResult對象的定義邏輯很簡單,就是包裝認證結果信息,好比AuthenticationTicket,它主要定義了咱們的基本認證信息,咱們能夠把它理解爲一張認證後的票據信息。AuthenticationProperties類型它主要定義了咱們認證相關的輔助信息,其中包括過時、重定向、持久等等信息。關於這兩個類型的定義我就不貼代碼了,其實現比較簡單。兜兜轉轉終於到了咱們的cooke認證明現類CookieAuthenticationHandler對象,下面咱們一塊兒看看它的定義。

 1 public class CookieAuthenticationHandler : SignInAuthenticationHandler<CookieAuthenticationOptions>
 2     {
 3         // 其餘代碼
 4         public CookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
 5             : base(options, logger, encoder, clock)
 6         { }
 7 
 8         protected override async Task<AuthenticateResult> HandleAuthenticateAsync();
 9 
10         protected virtual async Task FinishResponseAsync();
11 
12         protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
13 
14         protected async override Task HandleSignOutAsync(AuthenticationProperties properties);
15 
16         protected override async Task HandleForbiddenAsync(AuthenticationProperties properties);
17 
18         protected override async Task HandleChallengeAsync(AuthenticationProperties properties);
19     }

 

咱們暫且先不討論CookieAuthenticationHandler認證明現邏輯,由於整個認證結構,有涉及多個Handler對象,咱們仍是一步一步按照這個層次結構來介紹吧,至少你們不會以爲突兀。從CookieAuthenticationHandler的定義來看,它並未直接實現IAuthenticationHandler,仍是實現了有着以下定義的SignInAuthenticationHandler接口對象。

 1 public abstract class SignInAuthenticationHandler<TOptions> : SignOutAuthenticationHandler<TOptions>, IAuthenticationSignInHandler
 2         where TOptions : AuthenticationSchemeOptions, new()
 3     {
 4         public SignInAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
 5         { }
 6 
 7         public virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
 8         {
 9             var target = ResolveTarget(Options.ForwardSignIn);
10             return (target != null)
11                 ? Context.SignInAsync(target, user, properties)
12                 : HandleSignInAsync(user, properties ?? new AuthenticationProperties());
13         }
14 
15         protected abstract Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
16     }

 

從該對象的定義來看,它就是負責處理登陸相關處理的。其中還有SignOutAuthenticationHandler,處理邏輯相似,負責登出操做,可能有些朋友會以爲有點奇怪,爲何登入登出會單獨定義成相關接口,我的理解,其一站在業務的角度,登入、登出和認證仍是有必定的獨立性,並不是全部業務場景必需要先登陸才能實現認證,並且認證更多關注的是過程,其二以適應更多認證方式,把登入登出抽象出來,使其擴展更方便。它們派生自抽象類AuthenticationHandler<TOptions>,下面咱們看看它的定義。

  1 public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
  2     {
  3         // 其餘代碼
  4         protected AuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
  5         {
  6             Logger = logger.CreateLogger(this.GetType().FullName);
  7             UrlEncoder = encoder;
  8             Clock = clock;
  9             OptionsMonitor = options;
 10         }
 11 
 12         public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
 13         {
 14             if (scheme == null)
 15             {
 16                 throw new ArgumentNullException(nameof(scheme));
 17             }
 18             if (context == null)
 19             {
 20                 throw new ArgumentNullException(nameof(context));
 21             }
 22 
 23             Scheme = scheme;
 24             Context = context;
 25 
 26             Options = OptionsMonitor.Get(Scheme.Name);
 27 
 28             await InitializeEventsAsync();
 29             await InitializeHandlerAsync();
 30         }
 31 
 32         protected virtual async Task InitializeEventsAsync()
 33         {
 34             Events = Options.Events;
 35             if (Options.EventsType != null)
 36             {
 37                 Events = Context.RequestServices.GetRequiredService(Options.EventsType);
 38             }
 39             Events = Events ?? await CreateEventsAsync();
 40         }
 41 
 42         protected virtual Task<object> CreateEventsAsync() => Task.FromResult(new object());
 43 
 44         protected virtual Task InitializeHandlerAsync() => Task.CompletedTask;
 45 
 46         protected string BuildRedirectUri(string targetPath)
 47             => Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;
 48 
 49         protected virtual string ResolveTarget(string scheme)
 50         {
 51             var target = scheme ?? Options.ForwardDefaultSelector?.Invoke(Context) ?? Options.ForwardDefault;
 52 
 53             // Prevent self targetting
 54             return string.Equals(target, Scheme.Name, StringComparison.Ordinal)
 55                 ? null
 56                 : target;
 57         }
 58 
 59         public async Task<AuthenticateResult> AuthenticateAsync()
 60         {
 61             var target = ResolveTarget(Options.ForwardAuthenticate);
 62             if (target != null)
 63             {
 64                 return await Context.AuthenticateAsync(target);
 65             }
 66 
 67             var result = await HandleAuthenticateOnceAsync();
 68             if (result?.Failure == null)
 69             {
 70                 var ticket = result?.Ticket;
 71                 if (ticket?.Principal != null)
 72                 {
 73                     Logger.AuthenticationSchemeAuthenticated(Scheme.Name);
 74                 }
 75                 else
 76                 {
 77                     Logger.AuthenticationSchemeNotAuthenticated(Scheme.Name);
 78                 }
 79             }
 80             else
 81             {
 82                 Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Scheme.Name, result.Failure.Message);
 83             }
 84             return result;
 85         }
 86 
 87         protected Task<AuthenticateResult> HandleAuthenticateOnceAsync()
 88         {
 89             if (_authenticateTask == null)
 90             {
 91                 _authenticateTask = HandleAuthenticateAsync();
 92             }
 93 
 94             return _authenticateTask;
 95         }
 96 
 97         protected async Task<AuthenticateResult> HandleAuthenticateOnceSafeAsync()
 98         {
 99             try
100             {
101                 return await HandleAuthenticateOnceAsync();
102             }
103             catch (Exception ex)
104             {
105                 return AuthenticateResult.Fail(ex);
106             }
107         }
108 
109         protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
110 
111         protected virtual Task HandleForbiddenAsync(AuthenticationProperties properties)
112         {
113             Response.StatusCode = 403;
114             return Task.CompletedTask;
115         }
116 
117         protected virtual Task HandleChallengeAsync(AuthenticationProperties properties)
118         {
119             Response.StatusCode = 401;
120             return Task.CompletedTask;
121         }
122 
123         public async Task ChallengeAsync(AuthenticationProperties properties)
124         {
125             var target = ResolveTarget(Options.ForwardChallenge);
126             if (target != null)
127             {
128                 await Context.ChallengeAsync(target, properties);
129                 return;
130             }
131 
132             properties = properties ?? new AuthenticationProperties();
133             await HandleChallengeAsync(properties);
134             Logger.AuthenticationSchemeChallenged(Scheme.Name);
135         }
136 
137         public async Task ForbidAsync(AuthenticationProperties properties)
138         {
139             var target = ResolveTarget(Options.ForwardForbid);
140             if (target != null)
141             {
142                 await Context.ForbidAsync(target, properties);
143                 return;
144             }
145 
146             properties = properties ?? new AuthenticationProperties();
147             await HandleForbiddenAsync(properties);
148             Logger.AuthenticationSchemeForbidden(Scheme.Name);
149         }
150     }

 

該抽象類直接實現了咱們上面提到的認證接口IAuthenticationHandler,是NETCORE全部認證類的基類,而且提供相關默認實現。抽象方法HandleAuthenticateAsync就是咱們認證處理的入口,也是認證的核心實現,由具體的認證明現類實現。該基類的其餘方法,邏輯都比較簡單,或者只提供默認實現就再也不贅述,接下來咱們圍繞上面提到的cookie認證的核心實現類CookieAuthenticationHandler介紹其具體認證明現,在介紹其具體實現以前,咱們來看看它是如何被建立的或者說被注入到咱們的DI容器的,實際上是經過Startup的ConfigureServices方法註冊進來的,看代碼。

1 public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
2         {
3             builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
4             builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme).Validate(o => o.Cookie.Expiration == null, "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");
5             return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
6         }

 

經過CookieExtensions的擴展方法AddCookie方法注入進來的,有疑惑的朋友能夠看看我在開始介紹NETCORE認證的開始部分就貼出了這段代碼。接下來咱們繼續看認證核心部分。

  1 public class CookieAuthenticationHandler : SignInAuthenticationHandler<CookieAuthenticationOptions>
  2     {
  3         // 其餘成員
  4         public CookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
  5             : base(options, logger, encoder, clock)
  6         { }
  7 
  8         protected new CookieAuthenticationEvents Events
  9         {
 10             get { return (CookieAuthenticationEvents)base.Events; }
 11             set { base.Events = value; }
 12         }
 13         // 初始化handler,設置響應cookie回調
 14         protected override Task InitializeHandlerAsync()
 15         {
 16             // Cookies needs to finish the response
 17             Context.Response.OnStarting(FinishResponseAsync);
 18             return Task.CompletedTask;
 19         }
 20         // cookie認證各個處理階段,默認註冊的事件
 21         protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new CookieAuthenticationEvents());
 22         // 獲取ticket票據
 23         private Task<AuthenticateResult> EnsureCookieTicket();
 24         // 刷新票據
 25         private void CheckForRefresh(AuthenticationTicket ticket);
 26 
 27         private void RequestRefresh(AuthenticationTicket ticket, ClaimsPrincipal replacedPrincipal = null);
 28         // clone票據
 29         private AuthenticationTicket CloneTicket(AuthenticationTicket ticket, ClaimsPrincipal replacedPrincipal);
 30         
 31         private async Task<AuthenticateResult> ReadCookieTicket();
 32         // cookie認證
 33         protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
 34         {   
 35             // 獲取票據
 36             var result = await EnsureCookieTicket();
 37             if (!result.Succeeded)
 38             {
 39                 return result;
 40             }
 41             // 用戶信息驗證,默認沒有任何邏輯實現
 42             var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket);
 43             await Events.ValidatePrincipal(context);
 44 
 45             if (context.Principal == null)
 46             {
 47                 return AuthenticateResult.Fail("No principal.");
 48             }
 49             // 更新ticket票據,通常在以前會更新用戶信息
 50             if (context.ShouldRenew)
 51             {
 52                 RequestRefresh(result.Ticket, context.Principal);
 53             }
 54             // 認證成功,包裝result返回
 55             return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
 56         }
 57         // 寫入response
 58         protected virtual async Task FinishResponseAsync();
 59         // 登陸,
 60         protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
 61         {
 62             if (user == null)
 63             {
 64                 throw new ArgumentNullException(nameof(user));
 65             }
 66             // 獲取配置信息
 67             properties = properties ?? new AuthenticationProperties();
 68 
 69             _signInCalled = true;
 70 
 71             // 初始化,好比sessionkey
 72             await EnsureCookieTicket();
 73             var cookieOptions = BuildCookieOptions();
 74             // 建立cookiecontext,以備寫入response
 75             var signInContext = new CookieSigningInContext(
 76                 Context,
 77                 Scheme,
 78                 Options,
 79                 user,
 80                 properties,
 81                 cookieOptions);
 82             // 設置認證輔助信息,好比過時時間等等。
 83             DateTimeOffset issuedUtc;
 84             if (signInContext.Properties.IssuedUtc.HasValue)
 85             {
 86                 issuedUtc = signInContext.Properties.IssuedUtc.Value;
 87             }
 88             else
 89             {
 90                 issuedUtc = Clock.UtcNow;
 91                 signInContext.Properties.IssuedUtc = issuedUtc;
 92             }
 93 
 94             if (!signInContext.Properties.ExpiresUtc.HasValue)
 95             {
 96                 signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
 97             }
 98             // 執行signin階段處理事件,若是有重寫,執行重寫邏輯
 99             await Events.SigningIn(signInContext);
100             // 是否持久化
101             if (signInContext.Properties.IsPersistent)
102             {
103                 var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
104                 signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
105             }
106             // 建立認證票據ticket
107             var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name);
108             // 基於session邏輯,實現複雜的cookie信息緩存到服務端
109             if (Options.SessionStore != null)
110             {
111                 if (_sessionKey != null)
112                 {
113                     await Options.SessionStore.RemoveAsync(_sessionKey);
114                 }
115                 _sessionKey = await Options.SessionStore.StoreAsync(ticket);
116                 var principal = new ClaimsPrincipal(
117                     new ClaimsIdentity(
118                         new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
119                         Options.ClaimsIssuer));
120                 ticket = new AuthenticationTicket(principal, null, Scheme.Name);
121             }
122             // 加密票據
123             var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
124             // 設置response響應頭
125             Options.CookieManager.AppendResponseCookie(
126                 Context,
127                 Options.Cookie.Name,
128                 cookieValue,
129                 signInContext.CookieOptions);
130 
131             var signedInContext = new CookieSignedInContext(
132                 Context,
133                 Scheme,
134                 signInContext.Principal,
135                 signInContext.Properties,
136                 Options);
137             // 登陸後的事件處理
138             await Events.SignedIn(signedInContext);
139 
140             var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
141             await ApplyHeaders(shouldRedirect, signedInContext.Properties);
142 
143             Logger.AuthenticationSchemeSignedIn(Scheme.Name);
144         }
145         // 登出
146         protected async override Task HandleSignOutAsync(AuthenticationProperties properties);
147         
148         private async Task ApplyHeaders(bool shouldRedirectToReturnUrl, AuthenticationProperties properties);
149         // 權限不足
150         protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
151         {
152             var returnUrl = properties.RedirectUri;
153             if (string.IsNullOrEmpty(returnUrl))
154             {
155                 returnUrl = OriginalPathBase + OriginalPath + Request.QueryString;
156             }
157             var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl);
158             var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri));
159             await Events.RedirectToAccessDenied(redirectContext);
160         }
161         // 未認證用戶,訪問保護的資源
162         protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
163         {
164             // 經過配置信息獲取重定向url
165             var redirectUri = properties.RedirectUri;
166             if (string.IsNullOrEmpty(redirectUri))
167             {
168                 redirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
169             }
170             
171             var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
172             var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(loginUri));
173             // 重定向到登陸頁面
174             await Events.RedirectToLogin(redirectContext);
175         }
176     }

 

以上就是cookie認證的核心實現,代碼註釋比較詳細,接下來我大體描述一下cookie認證的處理邏輯,其實跟傳統的Forms或者Katana的cookie認證思路差很少。
1.首先獲取請求cookie,解密並建立ticket票據。
2.若是配置了sessionstore方案,經過sessionkey獲取用戶完整的聲明信息。
3.校驗過時,若是未過時。
4.更新cookie,條件爲過時時間範圍已過半。
5.校驗用戶信息,主要是針對cookie未失效,用戶聲明信息發生變動。
6.返回AuthenticateResult認證結果對象。
以上6點就是我我的針對NETCOREcookie認證的理解。接下來咱們一塊兒看看,認證中間件是如何關聯它們,實現咱們的系統認證。
認證中間件
下面咱們看看認證中間件的定義。

 1 public class AuthenticationMiddleware
 2     {
 3         #region Fields
 4 
 5         private readonly RequestDelegate _next;
 6 
 7         #endregion
 8 
 9         #region Ctor
10 
11         public AuthenticationMiddleware(IAuthenticationSchemeProvider schemes, RequestDelegate next)
12         {
13             Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
14             _next = next ?? throw new ArgumentNullException(nameof(next));
15         }
16 
17         #endregion
18 
19         #region Properties
20 
21         public IAuthenticationSchemeProvider Schemes { get; set; }
22 
23         #endregion
24 
25         #region Methods
26 
27         public async Task Invoke(HttpContext context)
28         {
29             context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
30             {
31                 OriginalPath = context.Request.Path,
32                 OriginalPathBase = context.Request.PathBase
33             });
34 
35             var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
36             foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
37             {
38                 try
39                 {
40                     if (await handlers.GetHandlerAsync(context, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync())
41                         return;
42                 }
43                 catch
44                 {
45                 }
46             }
47 
48             var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
49             if (defaultAuthenticate != null)
50             {
51                 var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
52                 if (result?.Principal != null)
53                 {
54                     context.User = result.Principal;
55                 }
56             }
57 
58             await _next(context);
59         }
60 
61         #endregion
62     }

 

如上認證中間件就是這麼簡單,關於中間件的原理能夠參看我上一篇帖子。
1.首先從DI裏面獲取IAuthenticationHandlerProvider的默認實現類AuthenticationHandlerProvider。
2.從schemes裏面獲取全部實現IAuthenticationRequestHandler接口的handler,沒有什麼特別的,就是多了一個請求方法,後續我會介紹,暫時咱們把它理解爲三方認證的實現handler。
3.若是有註冊該handler實例,將調用認證邏輯。
4.若是沒有註冊requesthandler實例,獲取默認scheme。
5.從指定的scheme裏面獲取具體認證handler實現認證。
6.若是認證成功,返回result,並賦值httpcontext.user屬性,完成認證。

最後總結

 

原本打算把NETCORE的受權也一併講完,實在想睡覺了,今天就到這吧。下面我來作個簡單的總結吧,關於NET平臺甚至NETCORE基於cookie認證的實現思路大體是同樣的,只是細節上面的區別,固然我理解的可能有些錯誤。咱們學習微軟web平臺的認證受權,其一是更好的掌握這個平臺,其二是學習他的設計思路,當咱們本身在實際開發中碰到安全相關的問題,如何去合理設計,更好的保證系統的安全性等等。

相關文章
相關標籤/搜索