原文连接: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 类是对 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 中注册了认证系统的三大核心对象:IAuthenticationSchemeProvider
,IAuthenticationHandlerProvider
和 IAuthenticationService
,以及一个对Claim进行转换的 IClaimsTransformation(不常用),下面就来介绍一下这三大对象。
首先来解释一下 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。
在 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 本质上是对 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中都有对应的实现,而SignInAsync
和SignOutAsync
则使用了独立的定义接口:
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 用来表示认证的结果:
它主要包含一个核心属性 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(笔记)
原文:https://www.cnblogs.com/fuyouchen/p/9560120.html