ASP.NET Core 认证与授权[1]:初识认证
在ASP.NET 4.X 中,我們最常用的是Forms認(rèn)證,它既可以用于局域網(wǎng)環(huán)境,也可用于互聯(lián)網(wǎng)環(huán)境,有著非常廣泛的使用。但是它很難進(jìn)行擴(kuò)展,更無(wú)法與第三方認(rèn)證集成,因此,在 ASP.NET Core 中對(duì)認(rèn)證與授權(quán)進(jìn)行了全新的設(shè)計(jì),并使用基于聲明的認(rèn)證(claims-based authentication),以適應(yīng)現(xiàn)代化應(yīng)用的需求。在運(yùn)行原理解剖[5]:Authentication中介紹了一下HttpContext與認(rèn)證系統(tǒng)的集成,本系列文章則來(lái)詳細(xì)介紹一下 ASP.NET Core 中認(rèn)證與授權(quán)。
目錄
- Claim
- ClaimsIdentity
- ClaimsPrincipal
- AuthenticationTicket
- Usage
- AddAuthentication
- AddScheme
- AddRemoteScheme
- UseAuthentication
- AuthenticationHandler
- RemoteAuthenticationHandler
基于聲明的認(rèn)證
Claim 通常被翻譯成聲明,但是感覺過(guò)于生硬,還是使用Claim來(lái)稱呼更加自然一些。記得是在MVC5中,第一次接觸到 “Claim" 的概念。在MVC5之前,我們所熟悉的是Windows認(rèn)證和Forms認(rèn)證,Windows認(rèn)證通常用于企業(yè)內(nèi)部,我們使用最多的還是Forms認(rèn)證,先來(lái)回顧一下,以前是怎么使用的:
首先我們會(huì)在web.config中配置認(rèn)證模式:
<authentication mode="Forms"><forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication>認(rèn)證票據(jù)的生成是使用FormsAuthentication來(lái)完成的:
FormsAuthentication.SetAuthCookie("bob", true);然后便可以通過(guò)HttpContext.User.Identity.Name獲取到當(dāng)前登錄用戶的名稱:"bob",那么它是如何來(lái)完成認(rèn)證的呢?
在 ASP.NET 4.x 中,我們應(yīng)該都對(duì) HttpModule 比較了解,它類似于 ASP.NET Core 中的中件間,ASP.NET 默認(rèn)會(huì)在全局的 administration.config 文件中注冊(cè)一大堆HttpModule,其中就包括WindowsAuthentication和FormsAuthentication,用來(lái)實(shí)現(xiàn)Windows認(rèn)證和Forms認(rèn)證:
<moduleProviders><!-- Server Modules--><add name="Authentication" type="Microsoft.Web.Management.Iis.Authentication.AuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add name="AnonymousAuthentication" type="Microsoft.Web.Management.Iis.Authentication.AnonymousAuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add name="BasicAuthentication" type="Microsoft.Web.Management.Iis.Authentication.BasicAuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add name="ActiveDirectoryAuthentication" type="Microsoft.Web.Management.Iis.Authentication.ActiveDirectoryAuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add name="WindowsAuthentication" type="Microsoft.Web.Management.Iis.Authentication.WindowsAuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><add name="DigestAuthentication" type="Microsoft.Web.Management.Iis.Authentication.DigestAuthenticationModuleProvider, Microsoft.Web.Management.Iis, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><!-- ASP.NET Modules--><add name="FormsAuthentication" type="Microsoft.Web.Management.AspNet.Authentication.FormsAuthenticationModuleProvider, Microsoft.Web.Management.Aspnet, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />可能大多人都不知道有這些Module,這也是微軟技術(shù)的一大弊端,總想著封裝成傻瓜化,造成入門容易,精通太難的局面。
如上,我們可以看到生成票據(jù)時(shí),默認(rèn)只能轉(zhuǎn)入一個(gè)Name,當(dāng)然也可以通過(guò)手動(dòng)創(chuàng)建FormsAuthenticationTicket來(lái)附帶一些額外的信息,但是都太過(guò)麻煩。
在傳統(tǒng)的身份認(rèn)證中,每個(gè)應(yīng)用程序都有它自己的驗(yàn)證用戶身份的方式,以及它自己的用戶數(shù)據(jù)庫(kù)。這種方式有很大的局限性,因?yàn)樗茈y集成多種認(rèn)證方式以支持用戶使用不同的方式來(lái)訪問(wèn)我們的應(yīng)用程序,比如組織內(nèi)的用戶(Windows-baseed 認(rèn)證),其它組織的用戶(Identity federation)或者是來(lái)自互聯(lián)網(wǎng)的用戶(Forms-based 認(rèn)證)等等。
而Claim 是關(guān)于一個(gè)人或組織的某個(gè)主題的陳述,比如:一個(gè)人的名稱,角色,個(gè)人喜好,種族,特權(quán),社團(tuán),能力等等。它本質(zhì)上就是一個(gè)鍵值對(duì),是一種非常通用的保存用戶信息的方式,可以很容易的將認(rèn)證和授權(quán)分離開來(lái),前者用來(lái)表示用戶是/不是什么,后者用來(lái)表示用戶能/不能做什么。
因此基于聲明的認(rèn)證有兩個(gè)主要的特點(diǎn):
將認(rèn)證與授權(quán)拆分成兩個(gè)獨(dú)立的服務(wù)。
在需要授權(quán)的服務(wù)中,不用再去關(guān)心你是如何認(rèn)證的,你用Windows認(rèn)證也好,Forms認(rèn)證也行,只要你出示你的 Claims 就行了。
ASP.NET Core 中的用戶身份
Claim
在 ASP.NET Core 中,使用Cliam類來(lái)表示用戶身份中的一項(xiàng)信息,它由核心的Type和Value屬性構(gòu)成:
public class Claim {private readonly string _type;private readonly string _value;public Claim(string type, string value): this(type, value, ClaimValueTypes.String, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, null, null, null){}internal Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject, string propertyKey, string propertyValue){...}public string Type => _type;public string Value => _value; }一個(gè)Claim可以是“用戶的姓名”,“郵箱地址”,“電話”,等等,而多個(gè)Claim構(gòu)成一個(gè)用戶的身份,使用ClaimsIdentity類來(lái)表示:
ClaimsIdentity
public class ClaimsIdentity : IIdentity { public virtual IEnumerable<Claim> Claims {get;}public virtual string AuthenticationType => _authenticationType;public virtual bool IsAuthenticated => !string.IsNullOrEmpty(_authenticationType);public virtual string Name{get{Claim claim = FindFirst(_nameClaimType);if (claim != null) return claim.Value;return null;}}}如上,其Name屬性用來(lái)查找Claims中,第一個(gè)Type為我們創(chuàng)建ClaimsIdentity時(shí)指定的NameClaimType的Claim的值,若未指定Type時(shí)則使用默認(rèn)的ClaimTypes.Name。而IsAuthenticated只是判斷_authenticationType是否為空,_authenticationType則對(duì)應(yīng)上一章中介紹的Scheme。
下面,我們演示一下用戶身份的創(chuàng)建:
// 創(chuàng)建一個(gè)用戶身份,注意需要指定AuthenticationType,否則IsAuthenticated將為false。 var claimIdentity = new ClaimsIdentity("myAuthenticationType"); // 添加幾個(gè)Claim claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "bob")); claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "bob@gmail.com")); claimIdentity.AddClaim(new Claim(ClaimTypes.MobilePhone, "18888888888"));如上,我們可以根據(jù)需要添加任意個(gè)的Claim,最后我們還需要再將用戶身份放到ClaimsPrincipal對(duì)象中。
ClaimsPrincipal
那么,ClaimsPrincipal是什么呢?在 ASP.NET 4.x 中我們可能對(duì)IPrincipal接口比較熟悉,在Controller中的User屬性便是IPrincipal類型:
public interface IPrincipal {IIdentity Identity { get; }bool IsInRole(string role); }可以看到IPrincipal除了包含用戶身份外,還有一個(gè)IsInRole方法,用于判斷用戶是否屬于指定角色,在基于角色的授權(quán)當(dāng)中便是調(diào)用此方法來(lái)實(shí)現(xiàn)的。
而在 ASP.NET Core 中,HttpContext直接使用的就是ClaimsPrincipal類型,而不再使用IPrincipal。
public abstract class HttpContext {public abstract ClaimsPrincipal User { get; set; } }而在ClaimsPrincipal中,可以包含多個(gè)用戶身份(ClaimsIdentity),除了對(duì)用戶身份的操作,還提供了針對(duì)Claims的查詢:
public class ClaimsPrincipal : IPrincipal {private readonly List<ClaimsIdentity> _identities = new List<ClaimsIdentity>();public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities) {_identities.AddRange(identities);}// 默認(rèn)從_identities中查找第一個(gè)不為空的ClaimsIdentity,也可以自定義查找方式。public virtual System.Security.Principal.IIdentity Identity {}// 查找_identities中是否包含類型為RoleClaimType(在創(chuàng)建ClaimsIdentity時(shí)指定,或者默認(rèn)的ClaimTypes.Role)的Claim。public virtual bool IsInRole(string role) {}// 獲取所有身份的Claim集合public virtual IEnumerable<Claim> Claims{get{foreach (ClaimsIdentity identity in Identities){foreach (Claim claim in identity.Claims){yield return claim;}}}} }ClaimsPrincipal的創(chuàng)建非常簡(jiǎn)單,只需傳入我們上面創(chuàng)建的用戶身份即可:
var principal = new ClaimsPrincipal(claimIdentity);由于HTTP是無(wú)狀態(tài)的,我們通常使用Cookie,請(qǐng)求頭或請(qǐng)求參數(shù)等方式來(lái)附加用戶的信息,在網(wǎng)絡(luò)上進(jìn)行傳輸,這就涉及到序列化和安全方面的問(wèn)題。因此,還需要將principal對(duì)象包裝成AuthenticationTicket對(duì)象。
AuthenticationTicket
當(dāng)我們創(chuàng)建完ClaimsPrincipal對(duì)象后,需要將它生成一個(gè)用戶票據(jù)并頒發(fā)給用戶,然后用戶拿著這個(gè)票據(jù),便可以訪問(wèn)受保持的資源,而在 ASP.NET Core 中,用戶票據(jù)用AuthenticationTicket來(lái)表示,如在Cookie認(rèn)證中,其認(rèn)證后的Cookie值便是對(duì)該對(duì)象序列化后的結(jié)果,它的定義如下:
public class AuthenticationTicket {public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties properties, string authenticationScheme){AuthenticationScheme = authenticationScheme;Principal = principal;Properties = properties ?? new AuthenticationProperties();}public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme) : this(principal, properties: null, authenticationScheme: authenticationScheme) { }public string AuthenticationScheme { get; private set; }public ClaimsPrincipal Principal { get; private set; }public AuthenticationProperties Properties { get; private set; } }用戶票據(jù)除了包含上面創(chuàng)建的principal對(duì)象外,還需要指定一個(gè)AuthenticationScheme (通常在授權(quán)中用來(lái)驗(yàn)證Scheme),并且還包含一個(gè)AuthenticationProperties對(duì)象,它主要是一些用戶票據(jù)安全方面的一些配置,如過(guò)期時(shí)間,是否持久等。
var properties = new AuthenticationProperties(); var ticket = new AuthenticationTicket(principal, properties, "myScheme"); // 加密 序列化 var token = Protect(ticket);最后,我們可以將票據(jù)(token)寫入到Cookie中,或是也可以以JSON的形式返回讓客戶端自行保存,由于我們對(duì)票據(jù)進(jìn)行了加密,可以保證在網(wǎng)絡(luò)中安全的傳輸而不會(huì)被篡改。
最終身份令牌的結(jié)構(gòu)大概是這樣的:
Microsoft.AspNetCore.Authentication
上面,我們介紹了身份票據(jù)的創(chuàng)建過(guò)程,下面就來(lái)介紹一下 ASP.NET Core 中的身份認(rèn)證。
ASP.NET Core 中的認(rèn)證系統(tǒng)具體實(shí)現(xiàn)在 Security 項(xiàng)目中,它包含 Cookie, JwtBearer, OAuth, OpenIdConnect 等:
認(rèn)證系統(tǒng)提供了非常靈活的擴(kuò)展,可以讓我們很容易的實(shí)現(xiàn)自定義認(rèn)證方式。
Usage
而對(duì)于認(rèn)證系統(tǒng)的配置,分為兩步,也是我們所熟悉的注冊(cè)服務(wù)和配置中間件:
首先,在DI中注冊(cè)服務(wù)認(rèn)證所需的服務(wù):
public void ConfigureServices(IServiceCollection services) {services.AddAuthentication(options =>{options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;}).AddCookie().AddOpenIdConnect(o =>{o.ClientId = "server.hybrid";o.ClientSecret = "secret";o.Authority = "https://demo.identityserver.io/";o.ResponseType = OpenIdConnectResponseType.CodeIdToken;}); }最后,注冊(cè)認(rèn)證中間件:
public void Configure(IApplicationBuilder app) {app.UseAuthentication(); }如上,我們的系統(tǒng)便支持了Cookie和JwtBearer兩種認(rèn)證方式,是不是非常簡(jiǎn)單,在我們的應(yīng)用程序中使用認(rèn)證系統(tǒng)時(shí),只需要調(diào)用 上一章 介紹的 HttpContext 中認(rèn)證相關(guān)的擴(kuò)展方法即可。
Microsoft.AspNetCore.Authentication,是所有認(rèn)證實(shí)現(xiàn)的公共抽象類,它定義了實(shí)現(xiàn)認(rèn)證Handler的規(guī)范,并包含一些共用的方法,如令牌加密,序列化等,AddAuthentication 便是其提供的統(tǒng)一的注冊(cè)認(rèn)證服務(wù)的擴(kuò)展方法:
AddAuthentication
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services) {services.AddAuthenticationCore();services.AddDataProtection();services.AddWebEncoders();services.TryAddSingleton<ISystemClock, SystemClock>();return new AuthenticationBuilder(services); }public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) {var builder = services.AddAuthentication();services.Configure(configureOptions);return builder; }如上,它首先會(huì)調(diào)用上一章中介紹的AddAuthenticationCore方法,然后注冊(cè)了DataProtection和WebEncoders兩個(gè)服務(wù)。而對(duì) AuthenticationOptions 我們之前在IAuthenticationSchemeProvider也介紹過(guò),它用來(lái)配置Scheme。
AddScheme
在上面的 AddAuthentication 中返回的是一個(gè)AuthenticationBuilder類型,所有認(rèn)證Handler的注冊(cè)都是以它的擴(kuò)展形式來(lái)實(shí)現(xiàn)的,它同時(shí)也提供了AddScheme擴(kuò)展方法,使我們可以更加方便的來(lái)配置Scheme:
public class AuthenticationBuilder {public AuthenticationBuilder(IServiceCollection services)=> Services = services;public virtual IServiceCollection Services { get; }public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(string authenticationScheme, Action<TOptions> configureOptions)where TOptions : AuthenticationSchemeOptions, new()where THandler : AuthenticationHandler<TOptions>=> AddScheme<TOptions, THandler>(authenticationScheme, displayName: null, configureOptions: configureOptions);public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)where TOptions : AuthenticationSchemeOptions, new()where THandler : AuthenticationHandler<TOptions>{Services.Configure<AuthenticationOptions>(o =>{o.AddScheme(authenticationScheme, scheme => {scheme.HandlerType = typeof(THandler);scheme.DisplayName = displayName;});});if (configureOptions != null){Services.Configure(authenticationScheme, configureOptions);}Services.AddTransient<THandler>();return this;} }在這里的AddScheme 擴(kuò)展方法只是封裝了對(duì)AuthenticationOptions中AddScheme的調(diào)用,如上面示例中的AddCookie便是調(diào)用該擴(kuò)展方法來(lái)實(shí)現(xiàn)的。
AddRemoteScheme
看到 Remote 我們應(yīng)該就可以猜到它是一種遠(yuǎn)程驗(yàn)證方式,先看一下它的定義:
public class AuthenticationBuilder {public virtual AuthenticationBuilder AddRemoteScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)where TOptions : RemoteAuthenticationOptions, new()where THandler : RemoteAuthenticationHandler<TOptions>{Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<TOptions>, EnsureSignInScheme<TOptions>>());return AddScheme<TOptions, THandler>(authenticationScheme, displayName, configureOptions: configureOptions);}private class EnsureSignInScheme<TOptions> : IPostConfigureOptions<TOptions> where TOptions : RemoteAuthenticationOptions{private readonly AuthenticationOptions _authOptions;public EnsureSignInScheme(IOptions<AuthenticationOptions> authOptions){_authOptions = authOptions.Value;}public void PostConfigure(string name, TOptions options){options.SignInScheme = options.SignInScheme ?? _authOptions.DefaultSignInScheme ?? _authOptions.DefaultScheme;if (string.Equals(options.SignInScheme, name, StringComparison.Ordinal)){throw new InvalidOperationException(Resources.Exception_RemoteSignInSchemeCannotBeSelf);}}} }首先使用PostConfigure模式(參見:Options[1]:Configure),對(duì)RemoteAuthenticationOptions進(jìn)行驗(yàn)證,要求遠(yuǎn)程驗(yàn)證中指定的SignInScheme不能為自身,這是為什么呢?后文再來(lái)解釋。然后便是直接調(diào)用上面介紹的 AddScheme 方法。
關(guān)于遠(yuǎn)程驗(yàn)證相對(duì)比較復(fù)雜,在本章中并不會(huì)太過(guò)深入的來(lái)介紹,在后續(xù)其它文章中會(huì)逐漸深入。
UseAuthentication
在上面,注冊(cè)認(rèn)證中間件時(shí),我們只需調(diào)用一個(gè)UseAuthentication擴(kuò)展方法,因?yàn)樗鼤?huì)執(zhí)行我們注冊(cè)的所有認(rèn)證Handler:
public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app) {return app.UseMiddleware<AuthenticationMiddleware>(); }咦,它的代碼好簡(jiǎn)單,只是注冊(cè)了一個(gè) AuthenticationMiddleware 而已,迫不及待的想看看它的實(shí)現(xiàn):
public class AuthenticationMiddleware {private readonly RequestDelegate _next;public IAuthenticationSchemeProvider Schemes { get; set; }public async Task Invoke(HttpContext context){context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature{OriginalPath = context.Request.Path,OriginalPathBase = context.Request.PathBase});var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()){var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;if (handler != null && await handler.HandleRequestAsync()){return;}}var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();if (defaultAuthenticate != null){var result = await context.AuthenticateAsync(defaultAuthenticate.Name);if (result?.Principal != null){context.User = result.Principal;}}await _next(context);} }很簡(jiǎn)單,但是很強(qiáng)大,不管我們是使用Cookie認(rèn)證,還是Bearer認(rèn)證,等等,都只需要這一個(gè)中間件,因?yàn)樗鼤?huì)解析所有的Handler來(lái)執(zhí)行。
不過(guò),在這里,這會(huì)先判斷是否具體實(shí)現(xiàn)了IAuthenticationRequestHandler的Hander,優(yōu)先來(lái)執(zhí)行,這個(gè)是什么鬼?
查了一下,發(fā)現(xiàn)IAuthenticationRequestHandler是在HttpAbstractions中定義的,只是在運(yùn)行原理解剖[5]:Authentication中沒有介紹到它:
public interface IAuthenticationRequestHandler : IAuthenticationHandler {Task<bool> HandleRequestAsync(); }它多了一個(gè)HandleRequestAsync方法,那么它存在的意義是什么呢?其實(shí)在Cookie認(rèn)證中并沒有用到它,它通常在遠(yuǎn)程認(rèn)證(如:OAuth, OIDC等)中使用,下文再來(lái)介紹。
繼續(xù)分析上面代碼,通過(guò)調(diào)用Schemes.GetDefaultAuthenticateSchemeAsync來(lái)獲取到認(rèn)證的Scheme,也就是上文提到的問(wèn)題,我們必須指定默認(rèn)的Scheme。
最后,調(diào)用AuthenticateAsync方法進(jìn)行認(rèn)證,認(rèn)證成功后,為HttpContext.User賦值,至于如何解析身份令牌生成ClaimsPrincipal對(duì)象,則交給相應(yīng)的Handler來(lái)處理。
認(rèn)證Handler
上文中多次提到認(rèn)證Handler,它由統(tǒng)一的AuthenticationMiddleware來(lái)調(diào)用,負(fù)責(zé)具體的認(rèn)證實(shí)現(xiàn),并分為本地認(rèn)證與遠(yuǎn)程認(rèn)證兩種方式。
在本地驗(yàn)證中,身份令牌的發(fā)放與認(rèn)證通常是由同一個(gè)服務(wù)器來(lái)完成,這也是我們比較熟悉的場(chǎng)景,對(duì)于Cookie, JwtBearer等認(rèn)證來(lái)說(shuō),都屬于是本地驗(yàn)證。而當(dāng)我們使用OAuth, OIDC等驗(yàn)證方式時(shí),身份令牌的發(fā)放則是由獨(dú)立的服務(wù)或是第三方(QQ, Weibo 等)認(rèn)證來(lái)提供,此時(shí)在我們的應(yīng)用程序中獲取身份令牌時(shí)需要請(qǐng)求遠(yuǎn)程服務(wù)器,因此稱之為遠(yuǎn)程驗(yàn)證。
AuthenticationHandler
AuthenticationHandler是所有認(rèn)證Handler的抽象基類,對(duì)于本地認(rèn)證直接實(shí)現(xiàn)該類即可,定義如下:
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new() {...public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context){...await InitializeEventsAsync();await InitializeHandlerAsync();}protected virtual async Task InitializeEventsAsync() { }protected virtual Task<object> CreateEventsAsync() => Task.FromResult(new object());protected virtual Task InitializeHandlerAsync() => Task.CompletedTask;public async Task<AuthenticateResult> AuthenticateAsync(){var result = await HandleAuthenticateOnceAsync();...}protected Task<AuthenticateResult> HandleAuthenticateOnceAsync(){if (_authenticateTask == null){_authenticateTask = HandleAuthenticateAsync();}return _authenticateTask;}protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();protected virtual Task HandleForbiddenAsync(AuthenticationProperties properties){Response.StatusCode = 403;return Task.CompletedTask;}protected virtual Task HandleChallengeAsync(AuthenticationProperties properties){Response.StatusCode = 401;return Task.CompletedTask;}... }如上,它定義一個(gè)抽象方法HandleAuthenticateAsync,并使用HandleAuthenticateOnceAsync方法來(lái)保證其在每次認(rèn)證只執(zhí)行一次。而HandleAuthenticateAsync是認(rèn)證的核心,交給具體的認(rèn)證Handler負(fù)責(zé)實(shí)現(xiàn)。而對(duì)于 ChallengeAsync, ForbidAsync 等方法也提供了默認(rèn)的實(shí)現(xiàn)。
而對(duì)于HandleAuthenticateAsync的實(shí)現(xiàn),大致的邏輯就是從請(qǐng)求中獲取上面發(fā)放的身份令牌,然后解析成AuthenticationTicket,并經(jīng)過(guò)一系列的驗(yàn)證,最終返回ClaimsPrincipal對(duì)象。
RemoteAuthenticationHandler
RemoteAuthenticationHandler 便是所有遠(yuǎn)程認(rèn)證的抽象基類了,它繼承自AuthenticationHandler,并實(shí)現(xiàn)了IAuthenticationRequestHandler接口:
public abstract class RemoteAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationRequestHandlerwhere TOptions : RemoteAuthenticationOptions, new() {public virtual Task<bool> ShouldHandleRequestAsync() => Task.FromResult(Options.CallbackPath == Request.Path);public virtual async Task<bool> HandleRequestAsync(){if (!await ShouldHandleRequestAsync()){return false;}var authResult = await HandleRemoteAuthenticateAsync();...await Context.SignInAsync(SignInScheme, ticketContext.Principal, ticketContext.Properties);if (string.IsNullOrEmpty(ticketContext.ReturnUri)) ticketContext.ReturnUri = "/";Response.Redirect(ticketContext.ReturnUri);return true;}protected abstract Task<HandleRequestResult> HandleRemoteAuthenticateAsync();protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){var result = await Context.AuthenticateAsync(SignInScheme);...}protected override Task HandleForbiddenAsync(AuthenticationProperties properties)=> Context.ForbidAsync(SignInScheme);protected virtual void GenerateCorrelationId(AuthenticationProperties properties) {}protected virtual bool ValidateCorrelationId(AuthenticationProperties properties) {} }在上面介紹的AuthenticationMiddleware中,提到它會(huì)先執(zhí)行實(shí)現(xiàn)了IAuthenticationRequestHandler 接口的Handler(遠(yuǎn)程認(rèn)證),之后(若未完成認(rèn)證)再執(zhí)行本地認(rèn)證Handler。
而RemoteAuthenticationHandler中核心的認(rèn)證邏輯便是 HandleRequestAsync 方法,它主要包含2個(gè)步驟:
首先執(zhí)行一個(gè)抽象方法HandleRemoteAuthenticateAsync,由具體的Handler來(lái)實(shí)現(xiàn),該方法返回的HandleRequestResult對(duì)象包含驗(yàn)證的結(jié)果(跳過(guò),失敗,成功等),在成功時(shí)會(huì)包含一個(gè)ticket對(duì)象。
若上一步驗(yàn)證成功,則根據(jù)返回的ticket,獲取到ClaimsPrincipal對(duì)象,并調(diào)用其它認(rèn)證Handler的Context.SignInAsync方法。
也就是說(shuō),遠(yuǎn)程Hander會(huì)在用戶未登錄時(shí),指引用戶跳轉(zhuǎn)到認(rèn)證服務(wù)器,登錄成功后,解析認(rèn)證服務(wù)器傳回的憑證,最終依賴于本地Handler來(lái)保存身份令牌。當(dāng)用戶再次訪問(wèn)則無(wú)需經(jīng)過(guò)遠(yuǎn)程Handler,直接交給本地Handler來(lái)處理。
由此也可以知道,遠(yuǎn)程認(rèn)證中本身并不具備SignIn的能力,所以必須通過(guò)指定其它SignInScheme交給本地認(rèn)證來(lái)完成 SignIn。
對(duì)于其父類的HandleAuthenticateAsync抽象方法則定義了一個(gè)默認(rèn)實(shí)現(xiàn):“直接轉(zhuǎn)交給本地驗(yàn)證來(lái)處理”。當(dāng)我們需要定義自己的遠(yuǎn)程認(rèn)證方式時(shí),通常只需實(shí)現(xiàn) HandleRemoteAuthenticateAsync 即可,而不用再去處理 HandleAuthenticateAsync 。
總結(jié)
基于聲明的認(rèn)證并不是微軟所特有的,它在國(guó)外被廣泛的使用,如微軟的ADFS,Google,Facebook,Twitter等等。在基于聲明的認(rèn)證中,對(duì)認(rèn)證和授權(quán)進(jìn)行了明確的區(qū)分,認(rèn)證用來(lái)頒發(fā)一個(gè)用戶的身份標(biāo)識(shí),其包含這個(gè)用戶的基本信息,而對(duì)于這個(gè)身份的頒發(fā)則由我們信任的第三方機(jī)構(gòu)來(lái)(STS)頒發(fā)(當(dāng)然,你也可以自己來(lái)頒發(fā))。而授權(quán),則是通過(guò)獲取身份標(biāo)識(shí)中的信息,來(lái)判斷該用戶能做什么,不能做什么。
本文對(duì) ASP.NET Core 中認(rèn)證系統(tǒng)的整個(gè)流程做了一個(gè)簡(jiǎn)要的介紹,可能會(huì)比較苦澀難懂,不過(guò)沒關(guān)系,大致有個(gè)印象就好,下一章則詳細(xì)介紹一下最常用的本地認(rèn)證方式:Cookie認(rèn)證,后續(xù)也會(huì)詳細(xì)介紹 OIDC 的用法與實(shí)現(xiàn),到時(shí)再回頭來(lái)看本文或許會(huì)豁然開朗。
轉(zhuǎn)載于:https://www.cnblogs.com/RainingNight/p/introduce-basic-authentication-in-asp-net-core.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 认证与授权[1]:初识认证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android 监听网络连接状态,判断网
- 下一篇: XML注释