这篇文章前前后后改了很多次,都是根据自己对asp.net core认证过程的不断深入,删删减减。
整个asp.net core在StartUp类的Configure方法中使用一个UseAuthentication()的方式将认证这个中间件添加到了应用中。这个中间件的源码很短,如下:
注:认证中间件的整体流程是查找远程认证的IAuthenticationHandler,如果没有找到,则查找一个默认的handler,都是通过scheme来查找的,scheme这个东西出现的原因是它是IAthenticationHandler和相关Options的总称。
public class AuthenticationMiddleware { private readonly RequestDelegate _next; public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes) { if (next == null) throw new ArgumentNullException(nameof (next)); if (schemes == null) throw new ArgumentNullException(nameof (schemes)); this._next = next; this.Schemes = schemes; } public IAuthenticationSchemeProvider Schemes { get; set; } public async Task Invoke(HttpContext context) { context.Features.Set<IAuthenticationFeature>((IAuthenticationFeature) new AuthenticationFeature() { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase }); IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (AuthenticationScheme authenticationScheme in await this.Schemes.GetRequestHandlerSchemesAsync())//GetReRequestHandlersAsync方法返回_requestHandlers字段保存的所有远程认证的Scheme { IAuthenticationRequestHandler handlerAsync = await handlers.GetHandlerAsync(context, authenticationScheme.Name) as IAuthenticationRequestHandler; bool flag = handlerAsync != null; if (flag) flag = await handlerAsync.HandleRequestAsync(); if (flag) return; } AuthenticationScheme authenticateSchemeAsync = await this.Schemes.GetDefaultAuthenticateSchemeAsync(); if (authenticateSchemeAsync != null) { AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticateSchemeAsync.Name); if (authenticateResult?.Principal != null) context.User = authenticateResult.Principal; } await this._next(context); } }
基本的流程是先找一个IAuthenticationRequestHandler,这个接口代表了远程认证,逻辑是①找到一个不为空的handler并执行HandlerRequestAsync;②如果执行结果为true,则返回,否则,继续下一个循环。如果没有找到任何IAuthenticationRequestHandler,则继续由GetDefaultAuthenticateSchemeAsync方法来找一个默认的本地认证Scheme,这个本地认证Scheme的逻辑是从AuthenticationOptions中先查看DefaultAuthenticationScheme是否有值(string),如果没有,再从DefaultScheme中查看是否有值(string)如果两者都没有,那么返回一个null。(注:认证中间件依赖一个IAuthenticationSchemeProvider(构造函数注入),后者的默认实现AuthenticationSchemeProvider依赖AuthenticationOptions类)。
整个asp.net core 认证的线索为IAuthenticationService,这个接口声明了5个动作方法,其中,具体的执行是由IAuthenticationHandler来执行的,这个接口定义了AuthenticateAsync()、ForbiddenAsync()和ChallengeAsync()。而SignInAsync和SignOutAsync则分别由IAuthenticationSignInHandler和IAuthenticationSignOutHandler来定义的。后两个接口也继承自IAuthenticationHandler,IAuthenticationHandler由IAuthenticationHandlerProvider来提供,IAuthenticationHandlerProvider使用IAuthenticationSchemeProvider来提供一个具体的AuthenticationScheme,AuthenticationScheme代表一个具体的方案,这个Scheme中包含了执行这个方案需要的HandlerType,也即是IAuthenticationHandler,从Scheme中拿到这个HandlerType之后,从DI或者ActivityUtils中得到具体的IAuthenticaitonHandler来执行最终逻辑。其他的诸如AuthenticateResult代表一个认证验证结果,他里面维护了一个AuthenticationTicket,还有一个AuthenticateProperties表示认证的一些特征,如颁发时间、过期时间等等。
这篇文章涉及的源码包括Microsoft.AspNETCore.Authentication.Abstraction、Microsoft.AspNETCore.Authentication.Core和Microsoft.AspNETCore.Authentication
认证和授权很相似,他们的英文也很相似,一个是Authentication认证,一个是Authorization授权。
asp.net core中的认证需要在Startup类中进行配置:
//ConfigureServices方法中: services.AddAuthentication(option => { option.DefaultScheme = "Cookie"; option.DefaultChallengeScheme = "Cookie"; option.DefaultAuthenticateScheme = "Cookie"; option.DefaultForbidScheme = "Cookie"; option.DefaultSignInScheme = "Cookie"; option.DefaultSignOutScheme = "Cookie"; }).AddCookie("Cookie", option => { option.LoginPath = "/Account/Login"; option.AccessDeniedPath = "/Account/Forbidden"; //....... });
//Configure方法中 app.UseAuthentication();
看一看到如果需要认证的话是需要分别在ConfigureService方法和Configure方法中分别进行配置的。
我们看到上面在AddAuthentication方法中配置了一个option,这个option是一个Action<AuthenticationOption>,在里面,写了一堆scheme。这个scheme是什么意思呢?我们先解释一下在asp.neet core中发生的这几个动作。在asp.net core中是有5个动作要发生的:
1、登陆(Signin):用户要进行登陆的动作。
2、登出(Signout):用户要进行登出。
3、Challenge:这个不好翻译,意思当用户需要请求一个被保护的资源时,系统要求用户进行登陆。总之他也是一个登陆的动作,但是被动的登陆,一般返回401。
4、Authenticate:认证,系统将用户的信息从token/cookie中读取出来。和登陆这个动作正好相反。
5、Forbid:系统对用户执行了拒绝的操作。一般返回403
上面这些动作最后都是由一个Handler来执行的,这个handler就是一个IAuthenticationHandler的实现。
我们先给出了上面的总结,再看一下具体的情况。asp.net core2.0开始上面的这些动作的执行都是通过HttpContext的扩展方法来执行的。我们拿登陆来说,其他都大同小异。
先看HttpContext.SigninAsync这个方法:
var claim = new Claim("name", "wallee");//claim相当于我的众多信息中的一个信息单元,还有年龄、性别、家庭等等 var identity = new ClaimsIdentity("身份证");//identity表示一个claim集合,这个集合代表了一个完整的“证件信息”,比如我的身份证 identity.AddClaim(claim);//将上面那个信息片段添加到我的身份证里面 var me=new ClaimsPrincipal(identity);//将身份证作为我个人的初始化参数,初始化一个ClaimsPrincipal就代表了我这个主体。还可以添加其他的identity,如还有驾驶证、准考证、会计证、计算机二级证等等
HttpContext.SignInAsync(me);//最后,利用这个主体,调用HttpContext的扩展方法进行登陆。
上面的代码中注释解释了一些和本文无关但又非常重要的信息,我们关键看最后哪一行:HttpContext.SigninAsync(principal);这行代码实现了最终的登陆。现在我们看一下它的实现:
public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) { return context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties); }
其实针对HttpContext的扩展方法都是调用IAuthenticationService来执行的,IAuthenticationService里面定义了针对上面描述的所有动作方法:
1 /// <summary>Used to provide authentication.</summary> 2 public interface IAuthenticationService 3 { 4 /// <summary>Authenticate for the specified authentication scheme.</summary> 5 /// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param> 6 /// <param name="scheme">The name of the authentication scheme.</param> 7 /// <returns>The result.</returns> 8 Task<AuthenticateResult> AuthenticateAsync( 9 HttpContext context, 10 string scheme); 11 12 /// <summary>Challenge the specified authentication scheme.</summary> 13 /// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param> 14 /// <param name="scheme">The name of the authentication scheme.</param> 15 /// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param> 16 /// <returns>A task.</returns> 17 Task ChallengeAsync( 18 HttpContext context, 19 string scheme, 20 AuthenticationProperties properties); 21 22 /// <summary>Forbids the specified authentication scheme.</summary> 23 /// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param> 24 /// <param name="scheme">The name of the authentication scheme.</param> 25 /// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param> 26 /// <returns>A task.</returns> 27 Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties); 28 29 /// <summary> 30 /// Sign a principal in for the specified authentication scheme. 31 /// </summary> 32 /// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param> 33 /// <param name="scheme">The name of the authentication scheme.</param> 34 /// <param name="principal">The <see cref="T:System.Security.Claims.ClaimsPrincipal" /> to sign in.</param> 35 /// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param> 36 /// <returns>A task.</returns> 37 Task SignInAsync( 38 HttpContext context, 39 string scheme, 40 ClaimsPrincipal principal, 41 AuthenticationProperties properties); 42 43 /// <summary>Sign out the specified authentication scheme.</summary> 44 /// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param> 45 /// <param name="scheme">The name of the authentication scheme.</param> 46 /// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param> 47 /// <returns>A task.</returns> 48 Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties); 49 }