1. 程式人生 > >ASP.NET Core [5]: Authentication(筆記)

ASP.NET Core [5]: Authentication(筆記)

login service 核心 體驗 ini .get aspnet eba 成功

原文連接:http://www.cnblogs.com/RainingNight/p/authentication-in-asp-net-core.html

在現代應用程序中,認證已不再是簡單的將用戶憑證保存在瀏覽器中,而要適應多種場景,如App,WebAPI,第三方登錄等等。在 ASP.NET 4.x 時代的Windows認證和Forms認證已無法滿足現代化的需求,因此在ASP.NET Core 中對認證及授權進行了全新設計,使其更加靈活,可以應付各種場景。在上一章中,我們提到HttpContext中認證相關的功能放在了獨立的模塊中,以擴展的方式來展現,以保證HttpContext的簡潔性,本章就來介紹一下 ASP.NET Core 認證系統的整個輪廓,以及它的切入點。

AuthenticationHttpContextExtensions

AuthenticationHttpContextExtensions 類是對 HttpContext 認證相關的擴展,它提供了如下擴展方法:

public static class AuthenticationHttpContextExtensions
{
    public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
        context.RequestServices.GetRequiredService
<IAuthenticationService>().AuthenticateAsync(context, scheme); public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { } public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { }
public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) {} public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { } public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) { } }

主要包括如上6個擴展方法,其它的只是一些參數重載:

  • SignInAsync 用戶登錄成功後頒發一個證書(加密的用戶憑證),用來標識用戶的身份。

  • SignOutAsync 退出登錄,如清除Coookie等。

  • AuthenticateAsync 驗證在 SignInAsync 中頒發的證書,並返回一個 AuthenticateResult 對象,表示用戶的身份。

  • ChallengeAsync 返回一個需要認證的標識來提示用戶登錄,通常會返回一個 401 狀態碼。

  • ForbidAsync 禁上訪問,表示用戶權限不足,通常會返回一個 403 狀態碼。

  • GetTokenAsync 用來獲取 AuthenticationProperties 中保存的額外信息。

它們的實現都非常簡單,與展示的第一個方法類似,從DI系統中獲取到 IAuthenticationService 接口實例,然後調用其同名方法。

因此,如果我們希望使用認證服務,那麽首先要註冊 IAuthenticationService 的實例,ASP.NET Core 中也提供了對應註冊擴展方法

public static class AuthenticationCoreServiceCollectionExtensions
{
    public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
    {
        services.TryAddScoped<IAuthenticationService, AuthenticationService>();
        services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
        services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
        services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
        return services;
    }

    public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) 
    {
        services.AddAuthenticationCore();
        services.Configure(configureOptions);
        return services;
    }
}

如上,AddAuthenticationCore 中註冊了認證系統的三大核心對象:IAuthenticationSchemeProviderIAuthenticationHandlerProviderIAuthenticationService,以及一個對Claim進行轉換的 IClaimsTransformation(不常用),下面就來介紹一下這三大對象。

IAuthenticationSchemeProvider

首先來解釋一下 Scheme 是用來做什麽的。因為在 ASP.NET Core 中可以支持各種各樣的認證方式(如,cookie, bearer, oauth, openid 等等),而 Scheme 用來標識使用的是哪種認證方式,不同的認證方式其處理方式是完全不一樣的,所以Scheme是非常重要的。

IAuthenticationSchemeProvider 用來提供對Scheme的註冊和查詢,其 AddScheme 方法,用來註冊Scheme,而每一種Scheme最終體現為一個 AuthenticationScheme 類型的對象: 每一個Scheme中還包含一個對應的IAuthenticationHandler類型的Handler,由它來完成具體的處理邏輯,看一下它的默認實現:

對於 AuthenticationOptions 對象,大家可能會比較熟悉,在上面介紹的 AddAuthenticationCore 擴展方法中,也是使用該對象來配置認證系統:

    public class AuthenticationOptions
    {
        public AuthenticationOptions();


        public IEnumerable<AuthenticationSchemeBuilder> Schemes { get; }

        public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; }

        public string DefaultScheme { get; set; }

        public string DefaultAuthenticateScheme { get; set; }

        public string DefaultSignInScheme { get; set; }
  
        public string DefaultSignOutScheme { get; set; }
       
        public string DefaultChallengeScheme { get; set; }

        public string DefaultForbidScheme { get; set; }

        public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder);

        public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler;
    }
            services.AddAuthentication(option =>{
                option.DefaultAuthenticateScheme=JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme=JwtBearerDefaults.AuthenticationScheme;
            })

該對象可以幫助我們更加方便的註冊Scheme,提供泛型和 AuthenticationSchemeBuilder 兩種方式配置方式。

到此,我們了解到,要想使用認證系統,必要先註冊Scheme,而每一個Scheme必須指定一個Handler,否則會拋出異常,下面我們就來了解一下Handler。

IAuthenticationHandlerProvider

在 ASP.NET Core 的認證系統中,AuthenticationHandler 負責對用戶憑證的驗證,它定義了如下接口:

public interface IAuthenticationHandler
{
    Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
    Task<AuthenticateResult> AuthenticateAsync();
    Task ChallengeAsync(AuthenticationProperties properties);
    Task ForbidAsync(AuthenticationProperties properties);
}

IAuthenticationService

IAuthenticationService 本質上是對 IAuthenticationSchemeProvider 和 IAuthenticationHandlerProvider 封裝,用來對外提供一個統一的認證服務接口:

public interface IAuthenticationService
{
    Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
    Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
    Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
    Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
    Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
}

這5個方法中,都需要接收一個 scheme 參數,因為只有先指定你要使用的認證方式,才能知道該如何進行認證。

對於上面的前三個方法,我們知道在IAuthenticationHandler中都有對應的實現,而SignInAsyncSignOutAsync則使用了獨立的定義接口:

public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
{
    Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}

public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
    Task SignOutAsync(AuthenticationProperties properties);
}

SignInAsync 和 SignOutAsync 之所以使用獨立的接口,是因為在現代架構中,通常會提供一個統一的認證中心,負責證書的頒發及銷毀(登入和登出),而其它服務只用來驗證證書,並用不到SingIn/SingOut。

而 IAuthenticationService 的默認實現 AuthenticationService 中的邏輯就非常簡單了,只是調用Handler中的同名方法:

AuthenticationService中對這5個方法的實現大致相同,首先會在我們傳入的scheme為null時,來獲取我們所註冊的默認scheme,然後獲取調用相應Handler的即可。針對 SignInAsyncSignOutAsync 的實現則會判斷Handler是否實現了對應的接口,若未實現則拋出異常。

AuthenticateResult

AuthenticateResult 用來表示認證的結果:

它主要包含一個核心屬性 AuthenticationTicket

public class AuthenticationTicket
{ 
    public string AuthenticationScheme { get; private set; }
    public ClaimsPrincipal Principal { get; private set; }
    public AuthenticationProperties Properties { get; private set; }
}

我們可以把AuthenticationTicket看成是一個經過認證後頒發的證書,

ClaimsPrincipal 屬性我們較為熟悉,表示證書的主體,在基於聲明的認證中,用來標識一個人的身份(如:姓名,郵箱等等),後續會詳細介紹一下基於聲明的認證。

AuthenticationProperties 屬性用來表示證書頒發的相關信息,如頒發時間,過期時間,重定向地址等等: 在上面最開始介紹的HttpContext中的 GetTokenAsync 擴展方法便是對AuthenticationProperties的擴展: oken擴展只是對AuthenticationProperties中的 Items 屬性進行添加和讀取。

下面我們演示一下 ASP.NET Core 認證系統的實際用法:

首先,我們要定義一個Handler:

public class MyHandler : IAuthenticationHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
{
    public AuthenticationScheme Scheme { get; private set; }
    protected HttpContext Context { get; private set; }

    public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
    {
        Scheme = scheme;
        Context = context;
        return Task.CompletedTask;
    }

    public async Task<AuthenticateResult> AuthenticateAsync()
    {
        var cookie = Context.Request.Cookies["mycookie"];
        if (string.IsNullOrEmpty(cookie))
        {
            return AuthenticateResult.NoResult();
        }
        return AuthenticateResult.Success(Deserialize(cookie));
    }

    public Task ChallengeAsync(AuthenticationProperties properties)
    {
        Context.Response.Redirect("/login");
        return Task.CompletedTask;
    }

    public Task ForbidAsync(AuthenticationProperties properties)
    {
        Context.Response.StatusCode = 403;
        return Task.CompletedTask;
    }

    public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
    {
        var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
        Context.Response.Cookies.Append("myCookie", Serialize(ticket));
        return Task.CompletedTask;
    }

    public Task SignOutAsync(AuthenticationProperties properties)
    {
        Context.Response.Cookies.Delete("myCookie");
        return Task.CompletedTask;
    }
}

如上,在 SignInAsync 中將用戶的Claim序列化後保存到Cookie中,在 AuthenticateAsync 中從Cookie中讀取並反序列化成用戶Claim。

然後在DI系統中註冊我們的Handler和Scheme:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthenticationCore(options => options.AddScheme<MyHandler>("myScheme", "demo scheme"));
}

最後,便可以通過HttpContext來調用認證系統了:

public void Configure(IApplicationBuilder app)
{
    // 登錄
    app.Map("/login", builder => builder.Use(next =>
    {
        return async (context) =>
        {
            var claimIdentity = new ClaimsIdentity();
            claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "jim"));
            await context.SignInAsync("myScheme", new ClaimsPrincipal(claimIdentity));
        };
    }));

    // 退出
    app.Map("/logout", builder => builder.Use(next =>
    {
        return async (context) =>
        {
            await context.SignOutAsync("myScheme");
        };
    }));

    // 認證
    app.Use(next =>
    {
        return async (context) =>
        {
            var result = await context.AuthenticateAsync("myScheme");
            if (result?.Principal != null) context.User = result.Principal;
            await next(context);
        };
    });

    // 授權
    app.Use(async (context, next) =>
    {
        var user = context.User;
        if (user?.Identity?.IsAuthenticated ?? false)
        {
            if (user.Identity.Name != "jim") await context.ForbidAsync("myScheme");
            else await next();
        }
        else
        {
            await context.ChallengeAsync("myScheme");
        }
    });

    // 訪問受保護資源
    app.Map("/resource", builder => builder.Run(async (context) => await context.Response.WriteAsync("Hello, ASP.NET Core!")));

在這裏完整演示了 ASP.NET Core 認證系統的基本用法,當然,在實際使用中要比這更加復雜,如安全性,易用性等方面的完善,但本質上也就這麽多東西。

總結

本章基於 HttpAbstractions 對 ASP.NET Core 認證系統做了一個簡單的介紹,但大多是一些抽象層次的定義,並未涉及到具體的實現。因為現實中有各種各樣的場景無法預測,HttpAbstractions 提供了統一的認證規範,在我們的應用程序中,可以根據具體需求來靈活的擴展適合的認證方式。不過在 Security 提供了更加具體的實現方式,也包含了 Cookie, JwtBearer, OAuth, OpenIdConnect 等較為常用的認證實現。在下個系列會來詳細介紹一下 ASP.NET Core 的認證與授權,更加偏向於實戰,敬請期待!

ASP.NET Core 在GitHub上的開源地址為:https://github.com/aspnet,包含了100多個項目,ASP.NET Core 的核心是 HttpAbstractions ,其它的都是圍繞著 HttpAbstractions 進行的擴展。本系列文章所涉及到的源碼只包含 Hosting 和 HttpAbstractions ,它們兩個已經構成了一個完整的 ASP.NET Core 運行時,不需要其它模塊,就可以輕松應對一些簡單的場景。當然,更多的時候我們還會使用比較熟悉的 Mvc 來大大提高開發速度和體驗,後續再來介紹一下MVC的運行方式。

ASP.NET Core [5]: Authentication(筆記)