ASP.NET Core [4]: Authentication(笔记)

摘要:
SignOutAsync注销,例如清除cookie。AuthenticateAsync验证SignInAsync中颁发的证书,并返回表示用户身份的AuthenticateResult对象。GetTokenAsync用于获取保存在AuthenticationProperties中的其他信息。IAuthenticationSchemeProvider首先解释Scheme的用途。因为ASP。NET Core,Scheme用于标识使用的身份验证方法。不同的身份验证方法有不同的处理方法,因此Scheme非常重要。

原文连接: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 中注册了认证系统的三大核心对象:IAuthenticationSchemeProviderIAuthenticationHandlerProvider 和 IAuthenticationService,以及一个对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的即可。针对 SignInAsync 和 SignOutAsync 的实现则会判断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 [4]: Authentication(笔记)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇mongodb 配置均衡器的运行窗口GoEasy接口测试Websocket下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

实现cookie跨域访问

  需求:A系统(www.a.com)里设置一个浏览器cookie,B系统(www.b.com)需要能够访问到A设置的cookie。   通过HTML SCRIPT标签跨域写cookie:     由于html的script标签的src属性,可以支持访问任何域的资源,通过script发起一个get类型的网络请求,获取资源。     A系统里加入: <...

CAS单点登录

1.      CAS 简介 1.1.  What is CAS ? CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。 CAS 开始于 2001 年, 并在 2004 年 12 月正式成为 JA...

Android,View转换bitmap,bitmap转换drawable

Android View转换Bitmap,Bitmap转换Drawable //测试设置bitmap View view1 = ViewGroup.inflate(context, R.layout.drawable_icon, null); TextView textView1 =view1.fin...

watch监听(数组或者对象)

handler:监听数组或对象的属性时用到的方法 deep:深度监听,为了发现对象内部值的变化,可以在选项参数中指定 deep:true 。注意监听数组的变动不需要这么做。 immediate: 在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调 tips: 只要bet中的属性发生变化(可被监测到的),便会执行handler函...

Spring Test 整合 JUnit 4 使用总结

转自:https://blog.csdn.net/hgffhh/article/details/83712924 这两天做Web开发,发现通过spring进行对象管理之后,做测试变得复杂了。因为所有的Bean都需要在applicationContext.xml中加载好,之后再通过@Resource去取得。如果每次都要整个业务流做的差不多了再去测试,这样效率...

解决cookie跨域访问

一、前言   随着项目模块越来越多,很多模块现在都是独立部署。模块之间的交流有时可能会通过cookie来完成。比如说门户和应用,分别部署在不同的机器或者web容器中,假如用户登陆之后会在浏览器客户端写入cookie(记录着用户上下文信息),应用想要获取门户下的cookie,这就产生了cookie跨域的问题。   二、介绍一下cookie   cookie ...