天天看點

ASP.NET Core 認證與授權系列(1)——初識認證

作者:IT技術資源愛好者

在ASP.NET 4.X 中,我們最常用的是Forms認證,它既可以用于區域網路環境,也可用于網際網路環境,有着非常廣泛的使用。但是它很難進行擴充,更無法與第三方認證內建,是以,在 ASP.NET Core 中對認證與授權進行了全新的設計,并使用基于聲明的認證(claims-based authentication),以适應現代化應用的需求。在運作原了解剖[5]:Authentication中介紹了一下HttpContext與認證系統的內建,本系列文章則來詳細介紹一下 ASP.NET Core 中認證與授權。

目錄

  1. 基于聲明的認證
  2. ASP.NET Core 中的使用者身份ClaimClaimsIdentityClaimsPrincipalAuthenticationTicket
  3. Microsoft.AspNetCore.AuthenticationUsageAddAuthenticationAddSchemeAddRemoteSchemeUseAuthentication
  4. 認證HandlerAuthenticationHandlerRemoteAuthenticationHandler

基于聲明的認證

Claim 通常被翻譯成聲明,但是感覺過于生硬,還是使用Claim來稱呼更加自然一些。記得是在MVC5中,第一次接觸到 “Claim" 的概念。在MVC5之前,我們所熟悉的是Windows認證和Forms認證,Windows認證通常用于企業内部,我們使用最多的還是Forms認證,先來回顧一下,以前是怎麼使用的:

首先我們會在web.config中配置認證模式:

<authentication mode="Forms">
    <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
           

認證票據的生成是使用FormsAuthentication來完成的:

FormsAuthentication.SetAuthCookie("bob", true);
           

然後便可以通過HttpContext.User.Identity.Name擷取到目前登入使用者的名稱:"bob",那麼它是如何來完成認證的呢?

在 ASP.NET 4.x 中,我們應該都對 HttpModule 比較了解,它類似于 ASP.NET Core 中的中件間,ASP.NET 預設會在全局的 administration.config 檔案中注冊一大堆HttpModule,其中就包括WindowsAuthentication和FormsAuthentication,用來實作Windows認證和Forms認證:

<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,這也是微軟技術的一大弊端,總想着封裝成傻瓜化,造成入門容易,精通太難的局面。

如上,我們可以看到生成票據時,預設隻能轉入一個Name,當然也可以通過手動建立FormsAuthenticationTicket來附帶一些額外的資訊,但是都太過麻煩。

在傳統的身份認證中,每個應用程式都有它自己的驗證使用者身份的方式,以及它自己的使用者資料庫。這種方式有很大的局限性,因為它很難內建多種認證方式以支援使用者使用不同的方式來通路我們的應用程式,比如組織内的使用者(Windows-baseed 認證),其它組織的使用者(Identity federation)或者是來自網際網路的使用者(Forms-based 認證)等等。

而Claim 是關于一個人或組織的某個主題的陳述,比如:一個人的名稱,角色,個人喜好,種族,特權,社團,能力等等。它本質上就是一個鍵值對,是一種非常通用的儲存使用者資訊的方式,可以很容易的将認證和授權分離開來,前者用來表示使用者是/不是什麼,後者用來表示使用者能/不能做什麼。

是以基于聲明的認證有兩個主要的特點:

  • 将認證與授權拆分成兩個獨立的服務。
  • 在需要授權的服務中,不用再去關心你是如何認證的,你用Windows認證也好,Forms認證也行,隻要你出示你的 Claims 就行了。

ASP.NET Core 中的使用者身份

Claim

在 ASP.NET Core 中,使用Cliam類來表示使用者身份中的一項資訊,它由核心的Type和Value屬性構成:

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;
}
           

一個Claim可以是“使用者的姓名”,“郵箱位址”,“電話”,等等,而多個Claim構成一個使用者的身份,使用ClaimsIdentity類來表示:

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屬性用來查找Claims中,第一個Type為我們建立ClaimsIdentity時指定的NameClaimType的Claim的值,若未指定Type時則使用預設的ClaimTypes.Name。而IsAuthenticated隻是判斷_authenticationType是否為空,_authenticationType則對應上一章中介紹的Scheme。

下面,我們示範一下使用者身份的建立:

// 建立一個使用者身份,注意需要指定AuthenticationType,否則IsAuthenticated将為false。
var claimIdentity = new ClaimsIdentity("myAuthenticationType");
// 添加幾個Claim
claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "bob"));
claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "[email protected]"));
claimIdentity.AddClaim(new Claim(ClaimTypes.MobilePhone, "18888888888"));
           

如上,我們可以根據需要添加任意個的Claim,最後我們還需要再将使用者身份放到ClaimsPrincipal對象中。

ClaimsPrincipal

那麼,ClaimsPrincipal是什麼呢?在 ASP.NET 4.x 中我們可能對IPrincipal接口比較熟悉,在Controller中的User屬性便是IPrincipal類型:

public interface IPrincipal
{
    IIdentity Identity { get; }
    bool IsInRole(string role);
}
           

可以看到IPrincipal除了包含使用者身份外,還有一個IsInRole方法,用于判斷使用者是否屬于指定角色,在基于角色的授權當中便是調用此方法來實作的。

而在 ASP.NET Core 中,HttpContext直接使用的就是ClaimsPrincipal類型,而不再使用IPrincipal。

public abstract class HttpContext
{
    public abstract ClaimsPrincipal User { get; set; }
}
           

而在ClaimsPrincipal中,可以包含多個使用者身份(ClaimsIdentity),除了對使用者身份的操作,還提供了針對Claims的查詢:

public class ClaimsPrincipal : IPrincipal
{
    private readonly List<ClaimsIdentity> _identities = new List<ClaimsIdentity>();

    public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities) 
    {
        _identities.AddRange(identities);
    }

    // 預設從_identities中查找第一個不為空的ClaimsIdentity,也可以自定義查找方式。
    public virtual System.Security.Principal.IIdentity Identity {}

    // 查找_identities中是否包含類型為RoleClaimType(在建立ClaimsIdentity時指定,或者預設的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的建立非常簡單,隻需傳入我們上面建立的使用者身份即可:

var principal = new ClaimsPrincipal(claimIdentity);
           

由于HTTP是無狀态的,我們通常使用Cookie,請求頭或請求參數等方式來附加使用者的資訊,在網絡上進行傳輸,這就涉及到序列化和安全方面的問題。是以,還需要将principal對象包裝成AuthenticationTicket對象。

AuthenticationTicket

當我們建立完ClaimsPrincipal對象後,需要将它生成一個使用者票據并頒發給使用者,然後使用者拿着這個票據,便可以通路受保持的資源,而在 ASP.NET Core 中,使用者票據用AuthenticationTicket來表示,如在Cookie認證中,其認證後的Cookie值便是對該對象序列化後的結果,它的定義如下:

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; }
}
           

使用者票據除了包含上面建立的principal對象外,還需要指定一個AuthenticationScheme (通常在授權中用來驗證Scheme),并且還包含一個AuthenticationProperties對象,它主要是一些使用者票據安全方面的一些配置,如過期時間,是否持久等。

var properties = new AuthenticationProperties();
var ticket = new AuthenticationTicket(principal, properties, "myScheme");
// 加密 序列化
var token = Protect(ticket);
           

最後,我們可以将票據(token)寫入到Cookie中,或是也可以以JSON的形式傳回讓用戶端自行儲存,由于我們對票據進行了加密,可以保證在網絡中安全的傳輸而不會被篡改。

最終身份令牌的結構大概是這樣的:

ASP.NET Core 認證與授權系列(1)——初識認證

Microsoft.AspNetCore.Authentication

上面,我們介紹了身份票據的建立過程,下面就來介紹一下 ASP.NET Core 中的身份認證。

ASP.NET Core 中的認證系統具體實作在 Security 項目中,它包含 Cookie, JwtBearer, OAuth, OpenIdConnect 等:

ASP.NET Core 認證與授權系列(1)——初識認證

認證系統提供了非常靈活的擴充,可以讓我們很容易的實作自定義認證方式。

Usage

而對于認證系統的配置,分為兩步,也是我們所熟悉的注冊服務和配置中間件:

首先,在DI中注冊服務認證所需的服務:

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;
    });
}
           

最後,注冊認證中間件:

public void Configure(IApplicationBuilder app)
{
    app.UseAuthentication();
}
           

如上,我們的系統便支援了Cookie和JwtBearer兩種認證方式,是不是非常簡單,在我們的應用程式中使用認證系統時,隻需要調用 上一章 介紹的 HttpContext 中認證相關的擴充方法即可。

Microsoft.AspNetCore.Authentication,是所有認證實作的公共抽象類,它定義了實作認證Handler的規範,并包含一些共用的方法,如令牌加密,序列化等,AddAuthentication 便是其提供的統一的注冊認證服務的擴充方法:

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;
}
           

如上,它首先會調用上一章中介紹的AddAuthenticationCore方法,然後注冊了DataProtection和WebEncoders兩個服務。而對 AuthenticationOptions 我們之前在IAuthenticationSchemeProvider也介紹過,它用來配置Scheme。

AddScheme

在上面的 AddAuthentication 中傳回的是一個AuthenticationBuilder類型,所有認證Handler的注冊都是以它的擴充形式來實作的,它同時也提供了AddScheme擴充方法,使我們可以更加友善的來配置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 擴充方法隻是封裝了對AuthenticationOptions中AddScheme的調用,如上面示例中的AddCookie便是調用該擴充方法來實作的。

AddRemoteScheme

看到 Remote 我們應該就可以猜到它是一種遠端驗證方式,先看一下它的定義:

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),對RemoteAuthenticationOptions進行驗證,要求遠端驗證中指定的SignInScheme不能為自身,這是為什麼呢?後文再來解釋。然後便是直接調用上面介紹的 AddScheme 方法。

關于遠端驗證相對比較複雜,在本章中并不會太過深入的來介紹,在後續其它文章中會逐漸深入。

UseAuthentication

在上面,注冊認證中間件時,我們隻需調用一個UseAuthentication擴充方法,因為它會執行我們注冊的所有認證Handler:

public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
{
    return app.UseMiddleware<AuthenticationMiddleware>();
}
           

咦,它的代碼好簡單,隻是注冊了一個 AuthenticationMiddleware 而已,迫不及待的想看看它的實作:

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);
    }
}
           

很簡單,但是很強大,不管我們是使用Cookie認證,還是Bearer認證,等等,都隻需要這一個中間件,因為它會解析所有的Handler來執行。

不過,在這裡,這會先判斷是否具體實作了IAuthenticationRequestHandler的Hander,優先來執行,這個是什麼鬼?

查了一下,發現IAuthenticationRequestHandler是在HttpAbstractions中定義的,隻是在運作原了解剖[5]:Authentication中沒有介紹到它:

public interface IAuthenticationRequestHandler : IAuthenticationHandler
{
    Task<bool> HandleRequestAsync();
}
           

它多了一個HandleRequestAsync方法,那麼它存在的意義是什麼呢?其實在Cookie認證中并沒有用到它,它通常在遠端認證(如:OAuth, OIDC等)中使用,下文再來介紹。

繼續分析上面代碼,通過調用Schemes.GetDefaultAuthenticateSchemeAsync來擷取到認證的Scheme,也就是上文提到的問題,我們必須指定預設的Scheme。

最後,調用AuthenticateAsync方法進行認證,認證成功後,為HttpContext.User指派,至于如何解析身份令牌生成ClaimsPrincipal對象,則交給相應的Handler來處理。

認證Handler

上文中多次提到認證Handler,它由統一的AuthenticationMiddleware來調用,負責具體的認證實作,并分為本地認證與遠端認證兩種方式。

在本地驗證中,身份令牌的發放與認證通常是由同一個伺服器來完成,這也是我們比較熟悉的場景,對于Cookie, JwtBearer等認證來說,都屬于是本地驗證。而當我們使用OAuth, OIDC等驗證方式時,身份令牌的發放則是由獨立的服務或是第三方(QQ, Weibo 等)認證來提供,此時在我們的應用程式中擷取身份令牌時需要請求遠端伺服器,是以稱之為遠端驗證。

AuthenticationHandler

AuthenticationHandler是所有認證Handler的抽象基類,對于本地認證直接實作該類即可,定義如下:

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;
    }

    ...
}
           

如上,它定義一個抽象方法HandleAuthenticateAsync,并使用HandleAuthenticateOnceAsync方法來保證其在每次認證隻執行一次。而HandleAuthenticateAsync是認證的核心,交給具體的認證Handler負責實作。而對于 ChallengeAsync, ForbidAsync 等方法也提供了預設的實作。

而對于HandleAuthenticateAsync的實作,大緻的邏輯就是從請求中擷取上面發放的身份令牌,然後解析成AuthenticationTicket,并經過一系列的驗證,最終傳回ClaimsPrincipal對象。

RemoteAuthenticationHandler

RemoteAuthenticationHandler 便是所有遠端認證的抽象基類了,它繼承自AuthenticationHandler,并實作了IAuthenticationRequestHandler接口:

public abstract class RemoteAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationRequestHandler
    where 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中,提到它會先執行實作了IAuthenticationRequestHandler 接口的Handler(遠端認證),之後(若未完成認證)再執行本地認證Handler。

而RemoteAuthenticationHandler中核心的認證邏輯便是 HandleRequestAsync 方法,它主要包含2個步驟:

  1. 首先執行一個抽象方法HandleRemoteAuthenticateAsync,由具體的Handler來實作,該方法傳回的HandleRequestResult對象包含驗證的結果(跳過,失敗,成功等),在成功時會包含一個ticket對象。
  2. 若上一步驗證成功,則根據傳回的ticket,擷取到ClaimsPrincipal對象,并調用其它認證Handler的Context.SignInAsync方法。

也就是說,遠端Hander會在使用者未登入時,指引使用者跳轉到認證伺服器,登入成功後,解析認證伺服器傳回的憑證,最終依賴于本地Handler來儲存身份令牌。當使用者再次通路則無需經過遠端Handler,直接交給本地Handler來處理。

由此也可以知道,遠端認證中本身并不具備SignIn的能力,是以必須通過指定其它SignInScheme交給本地認證來完成 SignIn。

對于其父類的HandleAuthenticateAsync抽象方法則定義了一個預設實作:“直接轉交給本地驗證來處理”。當我們需要定義自己的遠端認證方式時,通常隻需實作 HandleRemoteAuthenticateAsync 即可,而不用再去處理 HandleAuthenticateAsync 。

總結

基于聲明的認證并不是微軟所特有的,它在國外被廣泛的使用,如微軟的ADFS,Google,Facebook,Twitter等等。在基于聲明的認證中,對認證和授權進行了明确的區分,認證用來頒發一個使用者的身份辨別,其包含這個使用者的基本資訊,而對于這個身份的頒發則由我們信任的第三方機構來(STS)頒發(當然,你也可以自己來頒發)。而授權,則是通過擷取身份辨別中的資訊,來判斷該使用者能做什麼,不能做什麼。

本文對 ASP.NET Core 中認證系統的整個流程做了一個簡要的介紹,可能會比較苦澀難懂,不過沒關系,大緻有個印象就好,下一章則詳細介紹一下最常用的本地認證方式:Cookie認證,後續也會詳細介紹 OIDC 的用法與實作,到時再回頭來看本文或許會豁然開朗。