ASP.NETCore[源码分析篇]-Authentication认证
追本溯源,从使⽤开始
⾸先看⼀下我们通常是如何使⽤微软⾃带的认证,⼀般在Startup⾥⾯配置我们所需的依赖认证服务,这⾥通过JWT的认证⽅式讲解
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(authOpt =>
{
authOpt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authOpt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
//配置⾃⼰所要验证的参数
};
});
}
我们来看⼀下源码AddAuthentication主要做了什么
public static class AuthenticationServiceCollectionExtensions
{
public static AuthenticationBuilder AddAuthentication( this IServiceCollection services, Action<AuthenticationOptions> configureOptions)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
if (configureOptions == null)
throw new ArgumentNullException(nameof (configureOptions));
AuthenticationBuilder authenticationBuilder = services.AddAuthentication();
services.Configure<AuthenticationOptions>(configureOptions);
return authenticationBuilder;
}
public static AuthenticationBuilder AddAuthentication( this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
services.AddAuthenticationCore();
services.AddDataProtection();
services.AddWebEncoders();
services.TryAddSingleton<ISystemClock, SystemClock>();
return new AuthenticationBuilder(services);
}
public static AuthenticationBuilder AddAuthentication(
this IServiceCollection services,
string defaultScheme)
{
return services.AddAuthentication((Action<AuthenticationOptions>) (o => o.DefaultScheme = defaultScheme));
}
.....
}
ConfigureServices⽅法基本都是服务的注册,基于微软的风格,这⾥的AddAuthenticationCore肯定是我们的认证服务注册⽅法,来看⼀下
public static class AuthenticationCoreServiceCollectionExtensions
{
///<summary>
/// Add core authentication services needed for <see cref="T:Microsoft.AspNetCore.Authentication.IA
uthenticationService" />.
///</summary>
public static IServiceCollection AddAuthenticationCore(
this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
services.TryAddScoped<IAuthenticationService, AuthenticationService>();
services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>();
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
return services;
}
///<summary>
/// Add core authentication services needed for <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationService" />.
///</summary>
public static IServiceCollection AddAuthenticationCore(
this IServiceCollection services,
Action<AuthenticationOptions> configureOptions)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
if (configureOptions == null)
throw new ArgumentNullException(nameof (configureOptions));
services.AddAuthenticationCore();
services.Configure<AuthenticationOptions>(configureOptions);
return services;
}
}
我们看到这⾥主要注册了AuthenticationService, AuthenticationHandlerProvider, AuthenticationSchemeProvider这三个对象,如⽂章开头所说,追本溯源,从使⽤开始,我们先看⼀下这三个对象是如何在认证体系中使⽤的,且是如何发挥作⽤的。
从使⽤开始
看⼀下我们的认证管道构建
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseAuthentication();
...
}
public static class AuthAppBuilderExtensions
{
public static IApplicationBuilder UseAuthentication( this IApplicationBuilder app)
{
if (app == null)
throw new ArgumentNullException(nameof (app));
return app.UseMiddleware<AuthenticationMiddleware>();
}
}
这⾥使⽤了约定的注册⽅式UseMiddleware,并且指定使⽤中间件AuthenticationMiddleware
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())
{
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);
}
}
在继续往下之前,我们先看⼀下这个认证中间件的作⽤结果,当认证通过时,在HttpContext的User属性(ClaimPrincipal)赋予⾝份标识,所以在后续的请求管道中都是基于认证结果中的⾝份标识做鉴权,这个我们会在后⾯的实际操作中会提到。
⾔归正传,在这⾥引出了我们的两个对象AuthenticationHandlerProvider, AuthenticationSchemeProvider。
重要对象讲解
IAuthenticationSchemeProvider
从名字来看,IAuthenticationSchemeProvider的作⽤应该是提供Scheme的,这也是Provider在微软的风格⾥⾯起的作⽤(类似于⼯⼚模式)。
这个Scheme是什么呢?很明显,在Framework时代,也是有基于不同Scheme验证的,⽐如Bearer,Cookie,在Aspnet Core中定义不同的Scheme代表着不同的认证处理⽅式,具体体现是在每个Scheme中包含对应的IAuthenticationHandler类型的Handler,由它来完成跟⾃⾝Scheme相关的认证处理。如果没有定义会怎么样?仔细看上⾯这块源码,只有当AuthenticationScheme不为空时才会做认证,否则⼀旦在Controller打上鉴权标签[Authorize],将会直接返回401,所以我们必须指定⾃⼰的Scheme。
那么我们在哪⾥指定我们的Scheme类似呢?我们先返回到ConfigureService的AddJwtBearer,使⽤过的朋友们肯定知道,这⾥获取的Scheme是我们在ConfigureService通过Addxxx scheme指定的Scheme类型。这⾥我们是使⽤JWT的
在这⾥指定了TOptions 为JwtBearerOptions,⽽THandler为JwtBearerHandler。
public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(
string authenticationScheme,
string displayName,
Action<TOptions> configureOptions)
where TOptions : AuthenticationSchemeOptions, new()
where THandler : AuthenticationHandler<TOptions>
{
return this.AddSchemeHelper<TOptions, THandler>(authenticationScheme, displayName, configureOptions);
}
private AuthenticationBuilder AddSchemeHelper<TOptions, THandler>(
string authenticationScheme,
string displayName,
Action<TOptions> configureOptions)
where TOptions : class, new()
where THandler : class, IAuthenticationHandler
{
this.Services.Configure<AuthenticationOptions>((Action<AuthenticationOptions>) (o => o.AddScheme(authenticationScheme, (Action<AuthenticationSchemeBuilder>) (scheme =>
{
scheme.HandlerType = typeof (THandler);
scheme.DisplayName = displayName;
}))));
if (configureOptions != null)
this.Services.Configure<TOptions>(authenticationScheme, configureOptions);
this.Services.AddTransient<THandler>();
return this;
}
注意这⾥TOptions 是需要继承AuthenticationSchemeOptions的,在这⾥是JwtBearerOptions,⽽THandler是AuthenticationHandler<TOptions>类型的Handler,在这⾥是JwtBearerHandler。
我们回到Scheme的分析继续往下,⾸先看⼀下AuthenticationScheme的定义
public class AuthenticationScheme
{
///<summary>Constructor.</summary>
public AuthenticationScheme(string name, string displayName, Type handlerType)
{
if (name == null)
throw new ArgumentNullException(nameof (name));
if (handlerType == (Type) null)
throw new ArgumentNullException(nameof (handlerType));
if (!typeof (IAuthenticationHandler).IsAssignableFrom(handlerType))
throw new ArgumentException("handlerType must implement IAuthenticationHandler.");
this.Name = name;
this.HandlerType = handlerType;
this.DisplayName = displayName;
}
///<summary>The name of the authentication scheme.</summary>
public string Name { get; }
///<summary>
/// The display name for the scheme. Null is valid and used for non user facing schemes.
///</summary>
public string DisplayName { get; }
///<summary>
/// The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandler" /> type that handles this scheme.
///</summary>
public Type HandlerType { get; }
}
在这⾥可以看到,如果要使⽤Aspnet Core⾃⾝的认证体系,需先注册Scheme,并且该Scheme必须指定⼀个类型为IAuthenticationHandler的Handler,否则会抛出异常。(这个其实在AddxxxScheme的时候已经指定了AuthenticationHandler)
我们再看⼀下IAuthenticationSchemeProvider的GetRequestHandlerSchemesAsync⽅法做了什么
public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
{
return Task.FromResult<IEnumerable<AuthenticationScheme>>((IEnumerable<AuthenticationScheme>) this._requestHandlers);
}
这东西返回了_requestHandlers,这是什么?看代码
public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
{
private readonly object _lock = new object();
private readonly AuthenticationOptions _options;
private readonly IDictionary<string, AuthenticationScheme> _schemes;
private readonly List<AuthenticationScheme> _requestHandlers;
///<summary>
/// Creates an instance of <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider" />
/// using the specified <paramref name="options" />,
///</summary>
public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
: this(options, (IDictionary<string, AuthenticationScheme>) new Dictionary<string, AuthenticationScheme>((IEqualityComparer<string>) StringComparer.Ordinal))
{
}
///<summary>
/// Creates an instance of <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider" />
/
// using the specified <paramref name="options" /> and <paramref name="schemes" />.
///</summary>
protected AuthenticationSchemeProvider(
IOptions<AuthenticationOptions> options,
IDictionary<string, AuthenticationScheme> schemes)
{
this._options = options.Value;
IDictionary<string, AuthenticationScheme> dictionary = schemes;
if (dictionary == null)
throw new ArgumentNullException(nameof (schemes));
this._schemes = dictionary;
this._requestHandlers = new List<AuthenticationScheme>();
foreach (AuthenticationSchemeBuilder scheme in this._options.Schemes)
this.AddScheme(scheme.Build());
}
public virtual void AddScheme(AuthenticationScheme scheme)
{
if (this._schemes.ContainsKey(scheme.Name))
throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
lock (this._lock)
{
if (this._schemes.ContainsKey(scheme.Name))
throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
if (typeof (IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
this._requestHandlers.Add(scheme);
this._schemes[scheme.Name] = scheme;
}
}
.....
}
这东西就是把我们在认证注册服务中指定的scheme,通过解析出的AuthenticationSchemeProvider 的构造函数加载来的,进⽽返回⼀系列的
List<AuthenticationScheme>,OK拿到这些scheme之后有什么⽤呢?这⾥引出了我们的第⼆个对象AuthenticationHandlerProvider,下⾯我们来了解⼀下。
IAuthenticationHandlerProvider
我们看到,AuthenticationMiddleware中⽤到了IAuthenticationHandlerProvider的GetHandlerAsync⽅法,那我们先看⼀下这个⽅法的作⽤
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>) StringComparer.Ordinal);
///<summary>Constructor.</summary>
public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
{
this.Schemes = schemes;
}
///<summary>
/// The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.
///</summary>
public IAuthenticationSchemeProvider Schemes { get; }
///<summary>Returns the handler instance that will be used.</summary>
public async Task<IAuthenticationHandler> GetHandlerAsync( HttpContext context, string authenticationScheme)
{
if (this._handlerMap.ContainsKey(authenticationScheme))
return this._handlerMap[authenticationScheme];
AuthenticationScheme schemeAsync = await this.Schemes.GetSchemeAsync(authenticationScheme);
if (schemeAsync == null)
return (IAuthenticationHandler) null;
asp网页源码IAuthenticationHandler handler = (context.RequestServices.GetService(schemeAsync.HandlerType) ?? ActivatorUtilities.CreateInstance(context.RequestServices, schemeAsync.HandlerType)) as IAuthenticationHandler;
if (handler != null)
{
await handler.InitializeAsync(schemeAsync, context);
this._handlerMap[authenticationScheme] = handler;
}
return handler;
}
}
在创建Handler的时候,是先从AuthenticationScheme中获取,如果不存在则通过ActivatorUtilities创建。获取到Handle后,将会放在_handlerMap字典⾥⾯,当下
次获取Handler的时候,将直接从缓存中获取。
IAuthenticationService
这个对象是在AuthenticationMiddleware中最后才⽤到的,⽽且是基于HttpContext的扩展被调⽤
public static class AuthenticationHttpContextExtensions
{
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
.
...
}
这⾥主要调⽤了IAuthenticationService的AuthenticateAsync⽅法,看⼀下这个⽅法做了什么
public class AuthenticationService : IAuthenticationService
{
public IAuthenticationSchemeProvider Schemes { get; }
public IAuthenticationHandlerProvider Handlers { get; }
public IClaimsTransformation Transform { get; }
public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
if (scheme == null)
{
var scheme = (await this.Schemes.GetDefaultAuthenticateSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
}
var handler = await Handlers.GetHandlerAsync(context, scheme);
if(handler == null)
throw await this.CreateMissingHandlerException(scheme);
AuthenticateResult result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
return AuthenticateResult.Success(new AuthenticationTicket(await Transform.TransformAsync(result.Principal), result.Properties, result.Ticket.AuthenticationScheme));
return result;
}
}
这⾥其实就是我们在前⾯讲的根据Scheme获取对应的AuthenticationHandler,然后调⽤AuthenticateAsync()⽅法,这个⽅法调⽤了核⼼⽅法HandleAuthenticateOnceAsync,然后再调⽤HandleAuthenticateAsync()这个核⼼的认证⽅法。
从上图看到这个HandleAuthenticateAsync是个抽象⽅法,我们的⼦类都需要实现这个⽅法的动作,基于本⽂的例⼦,我们看⼀下JwtBearerHandler的⼀个实际认
证。
public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
JwtBearerHandler jwtBearerHandler = this;
string token = (string) null;
object obj;
AuthenticationFailedContext authenticationFailedContext;
int num;
try
{
MessageReceivedContext messageReceivedContext = new MessageReceivedContext(jwtBearerHandler.Context, jwtBearerHandler.Scheme, jwtBearerHandler.Options);
await jwtBearerHandler.Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
return messageReceivedContext.Result;
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
string header = (string) jwtBearerHandler.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(header))
return AuthenticateResult.NoResult();
if (header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
token = header.Substring("Bearer ".Length).Trim();
if (string.IsNullOrEmpty(token))
return AuthenticateResult.NoResult();
}
if (jwtBearerHandler._configuration == null && jwtBearerHandler.Options.ConfigurationManager != null)
{
OpenIdConnectConfiguration configurationAsync = await jwtBearerHandler.Options.ConfigurationManager.GetConfigurationAsync(jwtBearerHandler.Context.RequestAborted);
jwtBearerHandler._configuration = configurationAsync;
}
TokenValidationParameters validationParameters1 = jwtBearerHandler.Options.TokenValidationParameters.Clone();
if (jwtBearerHandler._configuration != null)
{
string[] strArray = new string[1]
{
jwtBearerHandler._configuration.Issuer
};
TokenValidationParameters validationParameters2 = validationParameters1;
IEnumerable<string> validIssuers = _ValidIssuers();
object obj1 = (validIssuers != null ? (object) validIssuers.Concat<string>((IEnumerable<string>) strArray) : (object) null) ?? (object) strArray;
validationParameters2.set_ValidIssuers((IEnumerable<string>) obj1);
TokenValidationParameters validationParameters3 = validationParameters1;
IEnumerable<SecurityKey> issuerSigningKeys = _IssuerSigningKeys();
IEnumerable<SecurityKey> securityKeys = (issuerSigningKeys != null ? issuerSigningKeys.Concat<SecurityKey>((IEnumerable<SecurityKey>) jwtBearerHandler.__SigningKeys()) : (IEnumerable<SecurityKey>) validationParameters3.set_IssuerSigningKeys(securityKeys);
}
List<Exception> exceptionList = (List<Exception>) null;
foreach (ISecurityTokenValidator securityTokenValidator in (IEnumerable<ISecurityTokenValidator>) jwtBearerHandler.Options.SecurityTokenValidators)
{
if (securityTokenValidator.CanReadToken(token))
{
SecurityToken securityToken;
ClaimsPrincipal claimsPrincipal;
try
{
claimsPrincipal = securityTokenValidator.ValidateToken(token, validationParameters1, ref securityToken);
}
catch (Exception ex)
{
jwtBearerHandler.Logger.TokenValidationFailed(ex);
if (jwtBearerHandler.Options.RefreshOnIssuerKeyNotFound && jwtBearerHandler.Options.ConfigurationManager != null && ex is SecurityTokenSignatureKeyNotFoundException)
jwtBearerHandler.Options.ConfigurationManager.RequestRefresh();
if (exceptionList == null)
exceptionList = new List<Exception>(1);
exceptionList.Add(ex);
continue;
}
jwtBearerHandler.Logger.TokenValidationSucceeded();
TokenValidatedContext validatedContext = new TokenValidatedContext(jwtBearerHandler.Context, jwtBearerHandler.Scheme, jwtBearerHandler.Options);
validatedContext.Principal = claimsPrincipal;
validatedContext.SecurityToken = securityToken;
TokenValidatedContext tokenValidatedContext = validatedContext;
await jwtBearerHandler.Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
return tokenValidatedContext.Result;
if (jwtBearerHandler.Options.SaveToken)
tokenValidatedContext.Properties.StoreTokens((IEnumerable<AuthenticationToken>) new AuthenticationToken[1]
{
new AuthenticationToken()
{
Name = "access_token",
Value = token
}
});
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (exceptionList == null)
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
authenticationFailedContext = new AuthenticationFailedContext(jwtBearerHandler.Context, jwtBearerHandler.Scheme, jwtBearerHandler.Options)
{
Exception = exceptionList.Count == 1 ? exceptionList[0] : (Exception) new AggregateException((IEnumerable<Exception>) exceptionList)
};
await jwtBearerHandler.Events.AuthenticationFailed(authenticationFailedContext);
return authenticationFailedContext.Result == null ? AuthenticateResult.Fail(authenticationFailedContext.Exception) : authenticationFailedContext.Result;
}
catch (Exception ex)
{
obj = (object) ex;
num = 1;
}
if (num == 1)
{
Exception ex = (Exception) obj;
jwtBearerHandler.Logger.ErrorProcessingMessage(ex);
authenticationFailedContext = new AuthenticationFailedContext(jwtBearerHandler.Context, jwtBearerHandler.Scheme, jwtBearerHandler.Options)
{
Exception = ex
};
await jwtBearerHandler.Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
return authenticationFailedContext.Result;
Exception source = obj as Exception;
if (source == null)
throw obj;
ExceptionDispatchInfo.Capture(source).Throw();
authenticationFailedContext = (AuthenticationFailedContext) null;
}
obj = (object) null;
token = (string) null;
AuthenticateResult authenticateResult;
return authenticateResult;
}
}
这个⽅法有点长,主要是从Request.Headers⾥⾯获取Authorization的Bearer出来解析,再在AddJwt
Bearer中传⼊的委托参数JwtBearerOptions的TokenValidationParameters属性作为依据进⾏对⽐来进⾏认证是否通过与否。
总结
本⽂对 ASP.NET Core 的认证流程做了⼀个源码分析流程介绍,由于是源码分析篇,所以可能会⽐较枯燥和苦涩难懂。在后⾯的真正使⽤过程中,然后再结合本篇的⼀个总结流程,相信⼤家会逐渐开朗。
1. 在Startup类中的ConfigureServices⽅法通过添加AddAuthentication注册我们最主要的三个对象AuthenticationService, AuthenticationHandlerProvider, AuthenticationSchemeProvider
2. 通过AddAuthentication返回的AuthenticationBuilder 通过AddJwtBearer(或者AddCookie)来指定Scheme类型和需要验证的参数
3. 在Startup类中的Configure⽅法通过添加UseAuthentication注册认证中间件
4. 在认证过程中,通过AuthenticationSchemeProvider获取正确的Scheme,在AuthenticationService中通过Scheme和AuthenticationHandlerProvider获取正确的AuthenticationHandler,最
后通过对应的AuthenticationHandler的AuthenticateAsync⽅法进⾏认证流程
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论