ASP.NET Core Authentication认证实现方法
追本溯源,从使用开始
首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解
publicvoidConfigureServices(IServiceCollectionservices)
{
services.AddAuthentication(authOpt=>
{
authOpt.DefaultAuthenticateScheme=JwtBearerDefaults.AuthenticationScheme;
authOpt.DefaultChallengeScheme=JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o=>
{
o.TokenValidationParameters=newTokenValidationParameters
{
//配置自己所要验证的参数
};
});
}
我们来看一下源码AddAuthentication主要做了什么
publicstaticclassAuthenticationServiceCollectionExtensions
{
publicstaticAuthenticationBuilderAddAuthentication(thisIServiceCollectionservices,ActionconfigureOptions)
{
if(services==null)
thrownewArgumentNullException(nameof(services));
if(configureOptions==null)
thrownewArgumentNullException(nameof(configureOptions));
AuthenticationBuilderauthenticationBuilder=services.AddAuthentication();
services.Configure(configureOptions);
returnauthenticationBuilder;
}
publicstaticAuthenticationBuilderAddAuthentication(thisIServiceCollectionservices)
{
if(services==null)
thrownewArgumentNullException(nameof(services));
services.AddAuthenticationCore();
services.AddDataProtection();
services.AddWebEncoders();
services.TryAddSingleton();
returnnewAuthenticationBuilder(services);
}
publicstaticAuthenticationBuilderAddAuthentication(
thisIServiceCollectionservices,
stringdefaultScheme)
{
returnservices.AddAuthentication((Action)(o=>o.DefaultScheme=defaultScheme));
}
.....
}
ConfigureServices方法基本都是服务的注册,基于微软的风格,这里的AddAuthenticationCore肯定是我们的认证服务注册方法,来看一下
publicstaticclassAuthenticationCoreServiceCollectionExtensions
{
///
///Addcoreauthenticationservicesneededfor .
///
publicstaticIServiceCollectionAddAuthenticationCore(
thisIServiceCollectionservices)
{
if(services==null)
thrownewArgumentNullException(nameof(services));
services.TryAddScoped();
services.TryAddSingleton();
services.TryAddScoped();
services.TryAddSingleton();
returnservices;
}
///
///Addcoreauthenticationservicesneededfor .
///
publicstaticIServiceCollectionAddAuthenticationCore(
thisIServiceCollectionservices,
ActionconfigureOptions)
{
if(services==null)
thrownewArgumentNullException(nameof(services));
if(configureOptions==null)
thrownewArgumentNullException(nameof(configureOptions));
services.AddAuthenticationCore();
services.Configure(configureOptions);
returnservices;
}
}
我们看到这里主要注册了AuthenticationService,AuthenticationHandlerProvider,AuthenticationSchemeProvider这三个对象,如文章开头所说,追本溯源,从使用开始,我们先看一下这三个对象是如何在认证体系中使用的,且是如何发挥作用的。
从使用开始
看一下我们的认证管道构建
publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory)
{
...
app.UseAuthentication();
...
}
publicstaticclassAuthAppBuilderExtensions
{
publicstaticIApplicationBuilderUseAuthentication(thisIApplicationBuilderapp)
{
if(app==null)
thrownewArgumentNullException(nameof(app));
returnapp.UseMiddleware();
}
}
这里使用了约定的注册方式UseMiddleware,并且指定使用中间件AuthenticationMiddleware
publicclassAuthenticationMiddleware
{
privatereadonlyRequestDelegate_next;
publicAuthenticationMiddleware(RequestDelegatenext,IAuthenticationSchemeProviderschemes)
{
if(next==null)
thrownewArgumentNullException(nameof(next));
if(schemes==null)
thrownewArgumentNullException(nameof(schemes));
this._next=next;
this.Schemes=schemes;
}
publicIAuthenticationSchemeProviderSchemes{get;set;}
publicasyncTaskInvoke(HttpContextcontext)
{
context.Features.Set((IAuthenticationFeature)newAuthenticationFeature()
{
OriginalPath=context.Request.Path,
OriginalPathBase=context.Request.PathBase
});
IAuthenticationHandlerProviderhandlers=context.RequestServices.GetRequiredService();
foreach(AuthenticationSchemeauthenticationSchemeinawaitthis.Schemes.GetRequestHandlerSchemesAsync())
{
IAuthenticationRequestHandlerhandlerAsync=awaithandlers.GetHandlerAsync(context,authenticationScheme.Name)asIAuthenticationRequestHandler;
boolflag=handlerAsync!=null;
if(flag)
flag=awaithandlerAsync.HandleRequestAsync();
if(flag)
return;
}
AuthenticationSchemeauthenticateSchemeAsync=awaitthis.Schemes.GetDefaultAuthenticateSchemeAsync();
if(authenticateSchemeAsync!=null)
{
AuthenticateResultauthenticateResult=awaitcontext.AuthenticateAsync(authenticateSchemeAsync.Name);//实际的认证业务
if(authenticateResult?.Principal!=null)
context.User=authenticateResult.Principal;
}
awaitthis._next(context);
}
}
在继续往下之前,我们先看一下这个认证中间件的作用结果,当认证通过时,在HttpContext的User属性(ClaimPrincipal)赋予身份标识,所以在后续的请求管道中都是基于认证结果中的身份标识做鉴权,这个我们会在后面的实际操作中会提到。
言归正传,在这里引出了我们的两个对象AuthenticationHandlerProvider,AuthenticationSchemeProvider。
重要对象讲解
IAuthenticationSchemeProvider
从名字来看,IAuthenticationSchemeProvider的作用应该是提供Scheme的,这也是Provider在微软的风格里面起的作用(类似于工厂模式)。
这个Scheme是什么呢?很明显,在Framework时代,也是有基于不同Scheme验证的,比如Bearer,Cookie,在AspnetCore中定义不同的Scheme代表着不同的认证处理方式,具体体现是在每个Scheme中包含对应的IAuthenticationHandler类型的Handler,由它来完成跟自身Scheme相关的认证处理。如果没有定义会怎么样?仔细看上面这块源码,只有当AuthenticationScheme不为空时才会做认证,否则一旦在Controller打上鉴权标签[Authorize],将会直接返回401,所以我们必须指定自己的Scheme。
那么我们在哪里指定我们的Scheme类似呢?我们先返回到ConfigureService的AddJwtBearer,使用过的朋友们肯定知道,这里获取的Scheme是我们在ConfigureService通过Addxxxscheme指定的Scheme类型。这里我们是使用JWT的
在这里指定了TOptions为JwtBearerOptions,而THandler为JwtBearerHandler。
publicvirtualAuthenticationBuilderAddScheme( stringauthenticationScheme, stringdisplayName, Action configureOptions) whereTOptions:AuthenticationSchemeOptions,new() whereTHandler:AuthenticationHandler { returnthis.AddSchemeHelper (authenticationScheme,displayName,configureOptions); } privateAuthenticationBuilderAddSchemeHelper ( stringauthenticationScheme, stringdisplayName, Action configureOptions) whereTOptions:class,new() whereTHandler:class,IAuthenticationHandler { this.Services.Configure ((Action )(o=>o.AddScheme(authenticationScheme,(Action )(scheme=> { scheme.HandlerType=typeof(THandler); scheme.DisplayName=displayName; })))); if(configureOptions!=null) this.Services.Configure (authenticationScheme,configureOptions); this.Services.AddTransient (); returnthis; }
注意这里TOptions是需要继承AuthenticationSchemeOptions的,在这里是JwtBearerOptions,而THandler是AuthenticationHandler
我们回到Scheme的分析继续往下,首先看一下AuthenticationScheme的定义
publicclassAuthenticationScheme
{
///Constructor.
publicAuthenticationScheme(stringname,stringdisplayName,TypehandlerType)
{
if(name==null)
thrownewArgumentNullException(nameof(name));
if(handlerType==(Type)null)
thrownewArgumentNullException(nameof(handlerType));
if(!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
thrownewArgumentException("handlerTypemustimplementIAuthenticationHandler.");
this.Name=name;
this.HandlerType=handlerType;
this.DisplayName=displayName;
}
///Thenameoftheauthenticationscheme.
publicstringName{get;}
///
///Thedisplaynameforthescheme.Nullisvalidandusedfornonuserfacingschemes.
///
publicstringDisplayName{get;}
///
///The typethathandlesthisscheme.
///
publicTypeHandlerType{get;}
}
在这里可以看到,如果要使用AspnetCore自身的认证体系,需先注册Scheme,并且该Scheme必须指定一个类型为IAuthenticationHandler的Handler,否则会抛出异常。(这个其实在AddxxxScheme的时候已经指定了AuthenticationHandler)
我们再看一下IAuthenticationSchemeProvider的GetRequestHandlerSchemesAsync方法做了什么
publicvirtualTask>GetRequestHandlerSchemesAsync() { returnTask.FromResult >((IEnumerable )this._requestHandlers); }
这东西返回了_requestHandlers,这是什么?看代码
publicclassAuthenticationSchemeProvider:IAuthenticationSchemeProvider
{
privatereadonlyobject_lock=newobject();
privatereadonlyAuthenticationOptions_options;
privatereadonlyIDictionary_schemes;
privatereadonlyList_requestHandlers;
///
///Createsaninstanceof
///usingthespecified ,
///
publicAuthenticationSchemeProvider(IOptionsoptions)
:this(options,(IDictionary)newDictionary((IEqualityComparer)StringComparer.Ordinal))
{
}
///
///Createsaninstanceof
///usingthespecified and .
///
protectedAuthenticationSchemeProvider(
IOptionsoptions,
IDictionaryschemes)
{
this._options=options.Value;
IDictionarydictionary=schemes;
if(dictionary==null)
thrownewArgumentNullException(nameof(schemes));
this._schemes=dictionary;
this._requestHandlers=newList();
foreach(AuthenticationSchemeBuilderschemeinthis._options.Schemes)
this.AddScheme(scheme.Build());
}
publicvirtualvoidAddScheme(AuthenticationSchemescheme)
{
if(this._schemes.ContainsKey(scheme.Name))
thrownewInvalidOperationException("Schemealreadyexists:"+scheme.Name);
lock(this._lock)
{
if(this._schemes.ContainsKey(scheme.Name))
thrownewInvalidOperationException("Schemealreadyexists:"+scheme.Name);
if(typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
this._requestHandlers.Add(scheme);
this._schemes[scheme.Name]=scheme;
}
}
.....
}
这东西就是把我们在认证注册服务中指定的scheme,通过解析出的AuthenticationSchemeProvider的构造函数加载来的,进而返回一系列的List
IAuthenticationHandlerProvider
我们看到,AuthenticationMiddleware中用到了IAuthenticationHandlerProvider的GetHandlerAsync方法,那我们先看一下这个方法的作用
publicclassAuthenticationHandlerProvider:IAuthenticationHandlerProvider
{
privateDictionary_handlerMap=newDictionary((IEqualityComparer)StringComparer.Ordinal);
///Constructor.
publicAuthenticationHandlerProvider(IAuthenticationSchemeProviderschemes)
{
this.Schemes=schemes;
}
///
///The .
///
publicIAuthenticationSchemeProviderSchemes{get;}
///Returnsthehandlerinstancethatwillbeused.
publicasyncTaskGetHandlerAsync(HttpContextcontext,stringauthenticationScheme)
{
if(this._handlerMap.ContainsKey(authenticationScheme))
returnthis._handlerMap[authenticationScheme];
AuthenticationSchemeschemeAsync=awaitthis.Schemes.GetSchemeAsync(authenticationScheme);
if(schemeAsync==null)
return(IAuthenticationHandler)null;
IAuthenticationHandlerhandler=(context.RequestServices.GetService(schemeAsync.HandlerType)??ActivatorUtilities.CreateInstance(context.RequestServices,schemeAsync.HandlerType))asIAuthenticationHandler;
if(handler!=null)
{
awaithandler.InitializeAsync(schemeAsync,context);
this._handlerMap[authenticationScheme]=handler;
}
returnhandler;
}
}
在创建Handler的时候,是先从AuthenticationScheme中获取,如果不存在则通过ActivatorUtilities创建。获取到Handle后,将会放在_handlerMap字典里面,当下次获取Handler的时候,将直接从缓存中获取。
IAuthenticationService
这个对象是在AuthenticationMiddleware中最后才用到的,而且是基于HttpContext的扩展被调用
publicstaticclassAuthenticationHttpContextExtensions
{
publicstaticTaskAuthenticateAsync(thisHttpContextcontext,stringscheme)=>
context.RequestServices.GetRequiredService().AuthenticateAsync(context,scheme);
....
}
这里主要调用了IAuthenticationService的AuthenticateAsync方法,看一下这个方法做了什么
publicclassAuthenticationService:IAuthenticationService
{
publicIAuthenticationSchemeProviderSchemes{get;}
publicIAuthenticationHandlerProviderHandlers{get;}
publicIClaimsTransformationTransform{get;}
publicvirtualasyncTaskAuthenticateAsync(HttpContextcontext,stringscheme)
{
if(scheme==null)
{
varscheme=(awaitthis.Schemes.GetDefaultAuthenticateSchemeAsync())?.Name;
if(scheme==null)
thrownewInvalidOperationException($"NoauthenticationSchemewasspecified,andtherewasnoDefaultAuthenticateSchemefound.");
}
varhandler=awaitHandlers.GetHandlerAsync(context,scheme);
if(handler==null)
throwawaitthis.CreateMissingHandlerException(scheme);
AuthenticateResultresult=awaithandler.AuthenticateAsync();
if(result!=null&&result.Succeeded)
returnAuthenticateResult.Success(newAuthenticationTicket(awaitTransform.TransformAsync(result.Principal),result.Properties,result.Ticket.AuthenticationScheme));
returnresult;
}
}
这里其实就是我们在前面讲的根据Scheme获取对应的AuthenticationHandler,然后调用AuthenticateAsync()方法,这个方法调用了核心方法HandleAuthenticateOnceAsync,然后再调用HandleAuthenticateAsync()这个核心的认证方法。
从上图看到这个HandleAuthenticateAsync是个抽象方法,我们的子类都需要实现这个方法的动作,基于本文的例子,我们看一下JwtBearerHandler的一个实际认证。
publicclassJwtBearerHandler:AuthenticationHandler{ protectedoverrideasyncTask HandleAuthenticateAsync() { JwtBearerHandlerjwtBearerHandler=this; stringtoken=(string)null; objectobj; AuthenticationFailedContextauthenticationFailedContext; intnum; try { MessageReceivedContextmessageReceivedContext=newMessageReceivedContext(jwtBearerHandler.Context,jwtBearerHandler.Scheme,jwtBearerHandler.Options); awaitjwtBearerHandler.Events.MessageReceived(messageReceivedContext); if(messageReceivedContext.Result!=null) returnmessageReceivedContext.Result; token=messageReceivedContext.Token; if(string.IsNullOrEmpty(token)) { stringheader=(string)jwtBearerHandler.Request.Headers["Authorization"]; if(string.IsNullOrEmpty(header)) returnAuthenticateResult.NoResult(); if(header.StartsWith("Bearer",StringComparison.OrdinalIgnoreCase)) token=header.Substring("Bearer".Length).Trim(); if(string.IsNullOrEmpty(token)) returnAuthenticateResult.NoResult(); } if(jwtBearerHandler._configuration==null&&jwtBearerHandler.Options.ConfigurationManager!=null) { OpenIdConnectConfigurationconfigurationAsync=awaitjwtBearerHandler.Options.ConfigurationManager.GetConfigurationAsync(jwtBearerHandler.Context.RequestAborted); jwtBearerHandler._configuration=configurationAsync; } TokenValidationParametersvalidationParameters1=jwtBearerHandler.Options.TokenValidationParameters.Clone(); if(jwtBearerHandler._configuration!=null) { string[]strArray=newstring[1] { jwtBearerHandler._configuration.Issuer }; TokenValidationParametersvalidationParameters2=validationParameters1; IEnumerable validIssuers=validationParameters1.get_ValidIssuers(); objectobj1=(validIssuers!=null?(object)validIssuers.Concat ((IEnumerable )strArray):(object)null)??(object)strArray; validationParameters2.set_ValidIssuers((IEnumerable )obj1); TokenValidationParametersvalidationParameters3=validationParameters1; IEnumerable issuerSigningKeys=validationParameters1.get_IssuerSigningKeys(); IEnumerable securityKeys=(issuerSigningKeys!=null?issuerSigningKeys.Concat ((IEnumerable )jwtBearerHandler._configuration.get_SigningKeys()):(IEnumerable )null)??(IEnumerable )jwtBearerHandler._configuration.get_SigningKeys(); validationParameters3.set_IssuerSigningKeys(securityKeys); } List exceptionList=(List )null; foreach(ISecurityTokenValidatorsecurityTokenValidatorin(IEnumerable )jwtBearerHandler.Options.SecurityTokenValidators) { if(securityTokenValidator.CanReadToken(token)) { SecurityTokensecurityToken; ClaimsPrincipalclaimsPrincipal; try { claimsPrincipal=securityTokenValidator.ValidateToken(token,validationParameters1,refsecurityToken); } catch(Exceptionex) { jwtBearerHandler.Logger.TokenValidationFailed(ex); if(jwtBearerHandler.Options.RefreshOnIssuerKeyNotFound&&jwtBearerHandler.Options.ConfigurationManager!=null&&exisSecurityTokenSignatureKeyNotFoundException) jwtBearerHandler.Options.ConfigurationManager.RequestRefresh(); if(exceptionList==null) exceptionList=newList (1); exceptionList.Add(ex); continue; } jwtBearerHandler.Logger.TokenValidationSucceeded(); TokenValidatedContextvalidatedContext=newTokenValidatedContext(jwtBearerHandler.Context,jwtBearerHandler.Scheme,jwtBearerHandler.Options); validatedContext.Principal=claimsPrincipal; validatedContext.SecurityToken=securityToken; TokenValidatedContexttokenValidatedContext=validatedContext; awaitjwtBearerHandler.Events.TokenValidated(tokenValidatedContext); if(tokenValidatedContext.Result!=null) returntokenValidatedContext.Result; if(jwtBearerHandler.Options.SaveToken) tokenValidatedContext.Properties.StoreTokens((IEnumerable )newAuthenticationToken[1] { newAuthenticationToken() { Name="access_token", Value=token } }); tokenValidatedContext.Success(); returntokenValidatedContext.Result; } } if(exceptionList==null) returnAuthenticateResult.Fail("NoSecurityTokenValidatoravailablefortoken:"+token??"[null]"); authenticationFailedContext=newAuthenticationFailedContext(jwtBearerHandler.Context,jwtBearerHandler.Scheme,jwtBearerHandler.Options) { Exception=exceptionList.Count==1?exceptionList[0]:(Exception)newAggregateException((IEnumerable )exceptionList) }; awaitjwtBearerHandler.Events.AuthenticationFailed(authenticationFailedContext); returnauthenticationFailedContext.Result==null?AuthenticateResult.Fail(authenticationFailedContext.Exception):authenticationFailedContext.Result; } catch(Exceptionex) { obj=(object)ex; num=1; } if(num==1) { Exceptionex=(Exception)obj; jwtBearerHandler.Logger.ErrorProcessingMessage(ex); authenticationFailedContext=newAuthenticationFailedContext(jwtBearerHandler.Context,jwtBearerHandler.Scheme,jwtBearerHandler.Options) { Exception=ex }; awaitjwtBearerHandler.Events.AuthenticationFailed(authenticationFailedContext); if(authenticationFailedContext.Result!=null) returnauthenticationFailedContext.Result; Exceptionsource=objasException; if(source==null) throwobj; ExceptionDispatchInfo.Capture(source).Throw(); authenticationFailedContext=(AuthenticationFailedContext)null; } obj=(object)null; token=(string)null; AuthenticateResultauthenticateResult; returnauthenticateResult; } }
这个方法有点长,主要是从Request.Headers里面获取Authorization的Bearer出来解析,再在AddJwtBearer中传入的委托参数JwtBearerOptions的TokenValidationParameters属性作为依据进行对比来进行认证是否通过与否。
总结
本文对ASP.NETCore的认证流程做了一个源码分析流程介绍,由于是源码分析篇,所以可能会比较枯燥和苦涩难懂。在后面的真正使用过程中,然后再结合本篇的一个总结流程,相信大家会逐渐开朗。
- 在Startup类中的ConfigureServices方法通过添加AddAuthentication注册我们最主要的三个对象AuthenticationService,AuthenticationHandlerProvider,AuthenticationSchemeProvider
- 通过AddAuthentication返回的AuthenticationBuilder通过AddJwtBearer(或者AddCookie)来指定Scheme类型和需要验证的参数
- 在Startup类中的Configure方法通过添加UseAuthentication注册认证中间件
- 在认证过程中,通过AuthenticationSchemeProvider获取正确的Scheme,在AuthenticationService中通过Scheme和AuthenticationHandlerProvider获取正确的AuthenticationHandler,最后通过对应的AuthenticationHandler的AuthenticateAsync方法进行认证流程
到此这篇关于ASP.NETCoreAuthentication认证实现方法的文章就介绍到这了,更多相关ASP.NETCoreAuthentication认证内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。