整体概况
整个asp.net core在StartUp类的Configure方法中使用一个UseAuthentication()的方式将认证这个中间件添加到了应用中。这个中间件的源码很短,如下:
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里面定义了针对上面描述的所有动作方法:
/// <summary>Used to provide authentication.</summary>
public interface IAuthenticationService
{
/// <summary>Authenticate for the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <returns>The result.</returns>
Task<AuthenticateResult> AuthenticateAsync(
HttpContext context,
string scheme); /// <summary>Challenge the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
Task ChallengeAsync(
HttpContext context,
string scheme,
AuthenticationProperties properties); /// <summary>Forbids the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties); /// <summary>
/// Sign a principal in for the specified authentication scheme.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="principal">The <see cref="T:System.Security.Claims.ClaimsPrincipal" /> to sign in.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
Task SignInAsync(
HttpContext context,
string scheme,
ClaimsPrincipal principal,
AuthenticationProperties properties); /// <summary>Sign out the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
}
IAuthenticateService在services.AddAuthentication()方法中以scoped的生命周期被注入到了DI,表示针对每次请求的新的实例。同时还被注入的有另外几个比较关键的接口:
services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>();//ClaimsTransFormation用来在全局将ClaimsPrincipal添加一些新的Claims,默认注入的这个是啥都没干
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();//AuthenticationHandler的提供者,提供执行Authenticatoin、Forbidden和Challenge等动作的handler
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();//AuthenticationScheme的提供者,AuthenticationScheme是对AuthenticationHandler的描述,通过Scheme找到相应的Handler
IAuthenticationService是对IAuthenticationSchemeProvider和IAuthenticationHandlerProvider的封装,它有一个AuthenticationService的实现:
/// <summary>
/// Implements <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationService" />.
/// </summary>
public class AuthenticationService : IAuthenticationService
{
/// <summary>Constructor.</summary>
/// <param name="schemes">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider" />.</param>
/// <param name="handlers">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationRequestHandler" />.</param>
/// <param name="transform">The <see cref="T:Microsoft.AspNetCore.Authentication.IClaimsTransformation" />.</param>
public AuthenticationService(
IAuthenticationSchemeProvider schemes,
IAuthenticationHandlerProvider handlers,
IClaimsTransformation transform)
{
this.Schemes = schemes;
this.Handlers = handlers;
this.Transform = transform;
} /// <summary>Used to lookup AuthenticationSchemes.</summary>
public IAuthenticationSchemeProvider Schemes { get; } /// <summary>Used to resolve IAuthenticationHandler instances.</summary>
public IAuthenticationHandlerProvider Handlers { get; } /// <summary>Used for claims transformation.</summary>
public IClaimsTransformation Transform { get; } /// <summary>Authenticate for the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <returns>The result.</returns>
public virtual async Task<AuthenticateResult> AuthenticateAsync(
HttpContext context,
string scheme)
{
if (scheme == null)
{
scheme = (await this.Schemes.GetDefaultAuthenticateSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException("No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
}
IAuthenticationHandler handlerAsync = await this.Handlers.GetHandlerAsync(context, scheme);
if (handlerAsync == null)
throw await this.CreateMissingHandlerException(scheme);
AuthenticateResult result = await handlerAsync.AuthenticateAsync();
return result != null && result.Succeeded ? AuthenticateResult.Success(new AuthenticationTicket(await this.Transform.TransformAsync(result.Principal), result.Properties, result.Ticket.AuthenticationScheme)) : result;
} /// <summary>Challenge the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
public virtual async Task ChallengeAsync(
HttpContext context,
string scheme,
AuthenticationProperties properties)
{
if (scheme == null)
{
scheme = (await this.Schemes.GetDefaultChallengeSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException("No authenticationScheme was specified, and there was no DefaultChallengeScheme found.");
}
IAuthenticationHandler handlerAsync = await this.Handlers.GetHandlerAsync(context, scheme);
if (handlerAsync == null)
throw await this.CreateMissingHandlerException(scheme);
await handlerAsync.ChallengeAsync(properties);
} /// <summary>Forbid the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
public virtual async Task ForbidAsync(
HttpContext context,
string scheme,
AuthenticationProperties properties)
{
if (scheme == null)
{
scheme = (await this.Schemes.GetDefaultForbidSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException("No authenticationScheme was specified, and there was no DefaultForbidScheme found.");
}
IAuthenticationHandler handlerAsync = await this.Handlers.GetHandlerAsync(context, scheme);
if (handlerAsync == null)
throw await this.CreateMissingHandlerException(scheme);
await handlerAsync.ForbidAsync(properties);
} /// <summary>
/// Sign a principal in for the specified authentication scheme.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="principal">The <see cref="T:System.Security.Claims.ClaimsPrincipal" /> to sign in.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
public virtual async Task SignInAsync(
HttpContext context,
string scheme,
ClaimsPrincipal principal,
AuthenticationProperties properties)
{
if (principal == null)
throw new ArgumentNullException(nameof (principal));
if (scheme == null)
{
scheme = (await this.Schemes.GetDefaultSignInSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException("No authenticationScheme was specified, and there was no DefaultSignInScheme found.");
}
IAuthenticationHandler handlerAsync = await this.Handlers.GetHandlerAsync(context, scheme);
if (handlerAsync == null)
throw await this.CreateMissingSignInHandlerException(scheme);
if (!(handlerAsync is IAuthenticationSignInHandler authenticationSignInHandler))
throw await this.CreateMismatchedSignInHandlerException(scheme, handlerAsync);
await authenticationSignInHandler.SignInAsync(principal, properties);
} /// <summary>Sign out the specified authentication scheme.</summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" />.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" />.</param>
/// <returns>A task.</returns>
public virtual async Task SignOutAsync(
HttpContext context,
string scheme,
AuthenticationProperties properties)
{
if (scheme == null)
{
scheme = (await this.Schemes.GetDefaultSignOutSchemeAsync())?.Name;
if (scheme == null)
throw new InvalidOperationException("No authenticationScheme was specified, and there was no DefaultSignOutScheme found.");
}
IAuthenticationHandler handlerAsync = await this.Handlers.GetHandlerAsync(context, scheme);
if (handlerAsync == null)
throw await this.CreateMissingSignOutHandlerException(scheme);
if (!(handlerAsync is IAuthenticationSignOutHandler authenticationSignOutHandler))
throw await this.CreateMismatchedSignOutHandlerException(scheme, handlerAsync);
await authenticationSignOutHandler.SignOutAsync(properties);
} private async Task<Exception> CreateMissingHandlerException(string scheme)
{
string str1 = string.Join(", ", (await this.Schemes.GetAllSchemesAsync()).Select<AuthenticationScheme, string>((Func<AuthenticationScheme, string>) (sch => sch.Name)));
string str2 = string.Format(" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{0}\",...)?", (object) scheme);
return !string.IsNullOrEmpty(str1) ? (Exception) new InvalidOperationException(string.Format("No authentication handler is registered for the scheme '{0}'. The registered schemes are: {1}.", (object) scheme, (object) str1) + str2) : (Exception) new InvalidOperationException("No authentication handlers are registered." + str2);
} private async Task<string> GetAllSignInSchemeNames()
{
return string.Join(", ", (await this.Schemes.GetAllSchemesAsync()).Where<AuthenticationScheme>((Func<AuthenticationScheme, bool>) (sch => typeof (IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))).Select<AuthenticationScheme, string>((Func<AuthenticationScheme, string>) (sch => sch.Name)));
} private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
{
string signInSchemeNames = await this.GetAllSignInSchemeNames();
string str = string.Format(" Did you forget to call AddAuthentication().AddCookies(\"{0}\",...)?", (object) scheme);
return !string.IsNullOrEmpty(signInSchemeNames) ? (Exception) new InvalidOperationException(string.Format("No sign-in authentication handler is registered for the scheme '{0}'. The registered sign-in schemes are: {1}.", (object) scheme, (object) signInSchemeNames) + str) : (Exception) new InvalidOperationException("No sign-in authentication handlers are registered." + str);
} private async Task<Exception> CreateMismatchedSignInHandlerException(
string scheme,
IAuthenticationHandler handler)
{
string signInSchemeNames = await this.GetAllSignInSchemeNames();
string str = string.Format("The authentication handler registered for scheme '{0}' is '{1}' which cannot be used for SignInAsync. ", (object) scheme, (object) handler.GetType().Name);
return !string.IsNullOrEmpty(signInSchemeNames) ? (Exception) new InvalidOperationException(str + string.Format("The registered sign-in schemes are: {0}.", (object) signInSchemeNames)) : (Exception) new InvalidOperationException(str + "Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
} private async Task<string> GetAllSignOutSchemeNames()
{
return string.Join(", ", (await this.Schemes.GetAllSchemesAsync()).Where<AuthenticationScheme>((Func<AuthenticationScheme, bool>) (sch => typeof (IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))).Select<AuthenticationScheme, string>((Func<AuthenticationScheme, string>) (sch => sch.Name)));
} private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
{
string signOutSchemeNames = await this.GetAllSignOutSchemeNames();
string str = string.Format(" Did you forget to call AddAuthentication().AddCookies(\"{0}\",...)?", (object) scheme);
return !string.IsNullOrEmpty(signOutSchemeNames) ? (Exception) new InvalidOperationException(string.Format("No sign-out authentication handler is registered for the scheme '{0}'. The registered sign-out schemes are: {1}.", (object) scheme, (object) signOutSchemeNames) + str) : (Exception) new InvalidOperationException("No sign-out authentication handlers are registered." + str);
} private async Task<Exception> CreateMismatchedSignOutHandlerException(
string scheme,
IAuthenticationHandler handler)
{
string signOutSchemeNames = await this.GetAllSignOutSchemeNames();
string str = string.Format("The authentication handler registered for scheme '{0}' is '{1}' which cannot be used for {2}. ", (object) scheme, (object) handler.GetType().Name, (object) "SignOutAsync");
return !string.IsNullOrEmpty(signOutSchemeNames) ? (Exception) new InvalidOperationException(str + string.Format("The registered sign-out schemes are: {0}.", (object) signOutSchemeNames)) : (Exception) new InvalidOperationException(str + string.Format("Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {0}(\"Cookies\",...)?", (object) "SignOutAsync"));
}
}
这个基本就是asp.net core 认证的整体概况了,先看一下整体的逻辑,在下一部分,我们再把一些相关的类型做一些补充说明。
可以看到AuthenticationService里面定义的这些AuthenticationAsync、ForbiddenAsync等等动作方法基本都遵循了一个逻辑:
①首先判断传入的scheme是否为null,如果为null,那么根据IAuthenticationSchemeProvider类型的Schemes属性来找出一个默认的scheme,如果上述两个都失败,则抛出异常。
②第一步成功的前提下,利用IAuthenticationHandlerProvider类型的Handlers属性根据找出的scheme来生成一个handler。
③在AuthenticationAsync、ForbiddenAsync和ChallengeAsync方法中,直接调用生成的handler的同名方法来执行最终的逻辑,而在SignInAsync和SignOutAsync中要判断拿到的这个Handler是否分别实现了IAuthenticationSignInHandler和IAuthenticationSignOutHandler这两个接口,如果没有实现,则爆出异常。
至于SignIn和SignOut这两个接口从IAuthenticatoinHandler中剥离出去是因为IAuthenticationHandler只是实现了对用户凭证的验证,至于SignIn和SignOut,现在大多数应用使用的登录方式都有所不同,况且这两个功能实现的是凭证的获取/发放,放在IAuthenticationHandler不合适。
public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler, IAuthenticationHandler
{
/// <summary>Handle sign in.</summary>
/// <param name="user">The <see cref="T:System.Security.Claims.ClaimsPrincipal" /> user.</param>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" /> that contains the extra meta-data arriving with the authentication.</param>
/// <returns>A task.</returns>
Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}
public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
/// <summary>Signout behavior.</summary>
/// <param name="properties">The <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" /> that contains the extra meta-data arriving with the authentication.</param>
/// <returns>A task.</returns>
Task SignOutAsync(AuthenticationProperties properties);
}
关键类型
AuthenticationScheme
public class AuthenticationScheme
{
/// <summary>Constructor.</summary>
/// <param name="name">The name for the authentication scheme.</param>
/// <param name="displayName">The display name for the authentication scheme.</param>
/// <param name="handlerType">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandler" /> type that handles this scheme.</param>
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; }
}
一个AuthenticationScheme就是一个对IAuthenticationHandler的描述,它为一个特定的handlerType分配了一个名称(name)。它的构造函数接收三个参数了分别初始化了内部的三个属性,并对这三个参数做了严格的校验保证输入的参数有效。
AuthenticationSchemeBuilder
public class AuthenticationSchemeBuilder
{
/// <summary>Constructor.</summary>
/// <param name="name">The name of the scheme being built.</param>
public AuthenticationSchemeBuilder(string name)
{
this.Name = name;
} /// <summary>The name of the scheme being built.</summary>
public string Name { get; } /// <summary>The display name for the scheme being built.</summary>
public string DisplayName { get; set; } /// <summary>
/// The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandler" /> type responsible for this scheme.
/// </summary>
public Type HandlerType { get; set; } /// <summary>
/// Builds the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationScheme" /> instance.
/// </summary>
/// <returns></returns>
public AuthenticationScheme Build()
{
return new AuthenticationScheme(this.Name, this.DisplayName, this.HandlerType);
}
}
从名字就能看出来它是AuthenticationScheme的建造者,它内部包含的三个属性和AuthenticationScheme是一样的。它的构造函数需要一个string类型的name参数来初始化name,没有对输入的参数做任何校验,另外毫无意外的它有一个Build方法,用自身的这三个属性来new一个AuthenticationScheme。
AutenticaitonOptions
public class AuthenticationOptions
{
private readonly IList<AuthenticationSchemeBuilder> _schemes = (IList<AuthenticationSchemeBuilder>) new List<AuthenticationSchemeBuilder>(); /// <summary>
/// Returns the schemes in the order they were added (important for request handling priority)
/// </summary>
public IEnumerable<AuthenticationSchemeBuilder> Schemes
{
get
{
return (IEnumerable<AuthenticationSchemeBuilder>) this._schemes;
}
} /// <summary>Maps schemes by name.</summary>
public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = (IDictionary<string, AuthenticationSchemeBuilder>) new Dictionary<string, AuthenticationSchemeBuilder>((IEqualityComparer<string>) StringComparer.Ordinal); /// <summary>
/// Adds an <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationScheme" />.
/// </summary>
/// <param name="name">The name of the scheme being added.</param>
/// <param name="configureBuilder">Configures the scheme.</param>
public void AddScheme(
string name,
Action<AuthenticationSchemeBuilder> configureBuilder)
{
if (name == null)
throw new ArgumentNullException(nameof (name));
if (configureBuilder == null)
throw new ArgumentNullException(nameof (configureBuilder));
if (this.SchemeMap.ContainsKey(name))
throw new InvalidOperationException("Scheme already exists: " + name);
AuthenticationSchemeBuilder authenticationSchemeBuilder = new AuthenticationSchemeBuilder(name);
configureBuilder(authenticationSchemeBuilder);
this._schemes.Add(authenticationSchemeBuilder);
this.SchemeMap[name] = authenticationSchemeBuilder;
} /// <summary>
/// Adds an <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationScheme" />.
/// </summary>
/// <typeparam name="THandler">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandler" /> responsible for the scheme.</typeparam>
/// <param name="name">The name of the scheme being added.</param>
/// <param name="displayName">The display name for the scheme.</param>
public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler
{
this.AddScheme(name, (Action<AuthenticationSchemeBuilder>) (b =>
{
b.DisplayName = displayName;
b.HandlerType = typeof (THandler);
}));
} /// <summary>
/// Used as the fallback default scheme for all the other defaults.
/// </summary>
public string DefaultScheme { get; set; } /// <summary>
/// Used as the default scheme by <see cref="M:Microsoft.AspNetCore.Authentication.IAuthenticationService.AuthenticateAsync(Microsoft.AspNetCore.Http.HttpContext,System.String)" />.
/// </summary>
public string DefaultAuthenticateScheme { get; set; } /// <summary>
/// Used as the default scheme by <see cref="M:Microsoft.AspNetCore.Authentication.IAuthenticationService.SignInAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,System.Security.Claims.ClaimsPrincipal,Microsoft.AspNetCore.Authentication.AuthenticationProperties)" />.
/// </summary>
public string DefaultSignInScheme { get; set; } /// <summary>
/// Used as the default scheme by <see cref="M:Microsoft.AspNetCore.Authentication.IAuthenticationService.SignOutAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties)" />.
/// </summary>
public string DefaultSignOutScheme { get; set; } /// <summary>
/// Used as the default scheme by <see cref="M:Microsoft.AspNetCore.Authentication.IAuthenticationService.ChallengeAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties)" />.
/// </summary>
public string DefaultChallengeScheme { get; set; } /// <summary>
/// Used as the default scheme by <see cref="M:Microsoft.AspNetCore.Authentication.IAuthenticationService.ForbidAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties)" />.
/// </summary>
public string DefaultForbidScheme { get; set; }
}
这个类为添加Scheme提供了便利,它维护了一个AuthenticationSchemeBuilder的列表和一个同类型的字典。------------------------
IAuthenticationHandlerProvider
public interface IAuthenticationHandlerProvider
{
Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme);
}
IAuthenticationHandler负责对用户的凭证进行校验,而这个Provider就是用来提供一个IAuthenticationHandler,它只有一个方法就是GetHandlerAsync(HttpContext context,string authenticationScheme)。从方法的参数传入也能知道IAuthenticationHnadlerProvider的实现依赖于AuthenticationScheme,确实,它的实现AuthenticationHandlerProvider里面确实维护了一个IAuthenticationSchemeProvider:
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>) StringComparer.Ordinal); /// <summary>Constructor.</summary>
/// <param name="schemes">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.</param>
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>
/// <param name="context">The context.</param>
/// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
/// <returns>The handler instance.</returns>
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;
if ((context.RequestServices.GetService(schemeAsync.HandlerType) ?? ActivatorUtilities.CreateInstance(context.RequestServices, schemeAsync.HandlerType)) is IAuthenticationHandler handler)
{
await handler.InitializeAsync(schemeAsync, context);
this._handlerMap[authenticationScheme] = handler;
}
return handler;
}
}
IAuthenticationHandlerProvider的目的很明确,就是要提供一个IAuthenticationHandler,它的逻辑如下:
①首先根据authenticationScheme查看_handlerMap是否包含相应的handler如果包含直接返回这个handler。
②如果没有从第一步中找到,那么就从Schemes属性中根据authenticationScheme找出相应的AuthenticationScheme,如果这个Scheme为空,那么直接返回一个null。
③如果从Schemes属性中找出的AuthenticationScheme不为空,那么或者从传入的HttpContext类型的context.RequestServices.GetService方法中找Handler,或者使用ActivatorUtilities来创建一个,不管使用那种方式都是从拿到的scheme中记录的handlerType来获取handler的。如果是一个IAuthenticationHandler,那么调用handler. InitializeAsync方法(注一)来初始化本身,并将这个handler放入自身的_handlerMap字典中。最后将这个handler返回。
AuthenticateResult
public class AuthenticateResult
{
protected AuthenticateResult()
{
} /// <summary>
/// If a ticket was produced, authenticate was successful.
/// </summary>
public bool Succeeded
{
get
{
return this.Ticket != null;
}
} /// <summary>The authentication ticket.</summary>
public AuthenticationTicket Ticket { get; protected set; } /// <summary>
/// Gets the claims-principal with authenticated user identities.
/// </summary>
public ClaimsPrincipal Principal
{
get
{
return this.Ticket?.Principal;
}
} /// <summary>
/// Additional state values for the authentication session.
/// </summary>
public AuthenticationProperties Properties { get; protected set; } /// <summary>Holds failure information from the authentication.</summary>
public Exception Failure { get; protected set; } /// <summary>
/// Indicates that there was no information returned for this authentication scheme.
/// </summary>
public bool None { get; protected set; } /// <summary>Indicates that authentication was successful.</summary>
/// <param name="ticket">The ticket representing the authentication result.</param>
/// <returns>The result.</returns>
public static AuthenticateResult Success(AuthenticationTicket ticket)
{
if (ticket == null)
throw new ArgumentNullException(nameof (ticket));
return new AuthenticateResult()
{
Ticket = ticket,
Properties = ticket.Properties
};
} /// <summary>
/// Indicates that there was no information returned for this authentication scheme.
/// </summary>
/// <returns>The result.</returns>
public static AuthenticateResult NoResult()
{
return new AuthenticateResult() { None = true };
} /// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failure">The failure exception.</param>
/// <returns>The result.</returns>
public static AuthenticateResult Fail(Exception failure)
{
return new AuthenticateResult() { Failure = failure };
} /// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failure">The failure exception.</param>
/// <param name="properties">Additional state values for the authentication session.</param>
/// <returns>The result.</returns>
public static AuthenticateResult Fail(
Exception failure,
AuthenticationProperties properties)
{
return new AuthenticateResult()
{
Failure = failure,
Properties = properties
};
} /// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failureMessage">The failure message.</param>
/// <returns>The result.</returns>
public static AuthenticateResult Fail(string failureMessage)
{
return AuthenticateResult.Fail(new Exception(failureMessage));
} /// <summary>
/// Indicates that there was a failure during authentication.
/// </summary>
/// <param name="failureMessage">The failure message.</param>
/// <param name="properties">Additional state values for the authentication session.</param>
/// <returns>The result.</returns>
public static AuthenticateResult Fail(
string failureMessage,
AuthenticationProperties properties)
{
return AuthenticateResult.Fail(new Exception(failureMessage), properties);
}
}
一个认证结果,表示AuthenticationService中的方法返回的结果。典型的状态模式。它内部维护一个AuthenticationTicket。可直接调用Success方法或这Fail方法来返回包含相应状态的Result。
AuthenticatoinTicket
public class AuthenticationTicket
{
/// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationTicket" /> class
/// </summary>
/// <param name="principal">the <see cref="T:System.Security.Claims.ClaimsPrincipal" /> that represents the authenticated user.</param>
/// <param name="properties">additional properties that can be consumed by the user or runtime.</param>
/// <param name="authenticationScheme">the authentication middleware that was responsible for this ticket.</param>
public AuthenticationTicket(
ClaimsPrincipal principal,
AuthenticationProperties properties,
string authenticationScheme)
{
if (principal == null)
throw new ArgumentNullException(nameof (principal));
this.AuthenticationScheme = authenticationScheme;
this.Principal = principal;
this.Properties = properties ?? new AuthenticationProperties();
} /// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationTicket" /> class
/// </summary>
/// <param name="principal">the <see cref="T:System.Security.Claims.ClaimsPrincipal" /> that represents the authenticated user.</param>
/// <param name="authenticationScheme">the authentication middleware that was responsible for this ticket.</param>
public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme)
: this(principal, (AuthenticationProperties) null, authenticationScheme)
{
} /// <summary>Gets the authentication type.</summary>
public string AuthenticationScheme { get; private set; } /// <summary>
/// Gets the claims-principal with authenticated user identities.
/// </summary>
public ClaimsPrincipal Principal { get; private set; } /// <summary>
/// Additional state values for the authentication session.
/// </summary>
public AuthenticationProperties Properties { get; private set; }
}
表示一个经过认证后颁发的证书,里面包含ClaimsPrincipal、AuthenticationScheme和AuthenticationProperties三个属性,ClaimsPrincipal表示证书的主体,如姓名、电话等等信息,AuthenticationProperties表示证书的相关信息,如颁发时间、过期时间和重定向地址等等。
AuthenticationProperties
public class AuthenticationProperties
{
internal const string IssuedUtcKey = ".issued";
internal const string ExpiresUtcKey = ".expires";
internal const string IsPersistentKey = ".persistent";
internal const string RedirectUriKey = ".redirect";
internal const string RefreshKey = ".refresh";
internal const string UtcDateTimeFormat = "r"; /// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" /> class.
/// </summary>
public AuthenticationProperties()
: this((IDictionary<string, string>) null, (IDictionary<string, object>) null)
{
} /// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" /> class.
/// </summary>
/// <param name="items">State values dictionary to use.</param>
public AuthenticationProperties(IDictionary<string, string> items)
: this(items, (IDictionary<string, object>) null)
{
} /// <summary>
/// Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationProperties" /> class.
/// </summary>
/// <param name="items">State values dictionary to use.</param>
/// <param name="parameters">Parameters dictionary to use.</param>
public AuthenticationProperties(
IDictionary<string, string> items,
IDictionary<string, object> parameters)
{
this.Items = items ?? (IDictionary<string, string>) new Dictionary<string, string>((IEqualityComparer<string>) StringComparer.Ordinal);
this.Parameters = parameters ?? (IDictionary<string, object>) new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.Ordinal);
} /// <summary>State values about the authentication session.</summary>
public IDictionary<string, string> Items { get; } /// <summary>
/// Collection of parameters that are passed to the authentication handler. These are not intended for
/// serialization or persistence, only for flowing data between call sites.
/// </summary>
public IDictionary<string, object> Parameters { get; } /// <summary>
/// Gets or sets whether the authentication session is persisted across multiple requests.
/// </summary>
public bool IsPersistent
{
get
{
return this.GetString(".persistent") != null;
}
set
{
this.SetString(".persistent", value ? string.Empty : (string) null);
}
} /// <summary>
/// Gets or sets the full path or absolute URI to be used as an http redirect response value.
/// </summary>
public string RedirectUri
{
get
{
return this.GetString(".redirect");
}
set
{
this.SetString(".redirect", value);
}
} /// <summary>
/// Gets or sets the time at which the authentication ticket was issued.
/// </summary>
public DateTimeOffset? IssuedUtc
{
get
{
return this.GetDateTimeOffset(".issued");
}
set
{
this.SetDateTimeOffset(".issued", value);
}
} /// <summary>
/// Gets or sets the time at which the authentication ticket expires.
/// </summary>
public DateTimeOffset? ExpiresUtc
{
get
{
return this.GetDateTimeOffset(".expires");
}
set
{
this.SetDateTimeOffset(".expires", value);
}
} /// <summary>
/// Gets or sets if refreshing the authentication session should be allowed.
/// </summary>
public bool? AllowRefresh
{
get
{
return this.GetBool(".refresh");
}
set
{
this.SetBool(".refresh", value);
}
} /// <summary>
/// Get a string value from the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
public string GetString(string key)
{
string str;
return !this.Items.TryGetValue(key, out str) ? (string) null : str;
} /// <summary>
/// Set a string value in the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <param name="value">Value to set or <c>null</c> to remove the property.</param>
public void SetString(string key, string value)
{
if (value != null)
{
this.Items[key] = value;
}
else
{
if (!this.Items.ContainsKey(key))
return;
this.Items.Remove(key);
}
} /// <summary>
/// Get a parameter from the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Parameters" /> collection.
/// </summary>
/// <typeparam name="T">Parameter type.</typeparam>
/// <param name="key">Parameter key.</param>
/// <returns>Retrieved value or the default value if the property is not set.</returns>
public T GetParameter<T>(string key)
{
object obj1;
return this.Parameters.TryGetValue(key, out obj1) && obj1 is T obj2 ? obj2 : default (T);
} /// <summary>
/// Set a parameter value in the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Parameters" /> collection.
/// </summary>
/// <typeparam name="T">Parameter type.</typeparam>
/// <param name="key">Parameter key.</param>
/// <param name="value">Value to set.</param>
public void SetParameter<T>(string key, T value)
{
this.Parameters[key] = (object) value;
} /// <summary>
/// Get a bool value from the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
protected bool? GetBool(string key)
{
string str;
bool result;
return this.Items.TryGetValue(key, out str) && bool.TryParse(str, out result) ? new bool?(result) : new bool?();
} /// <summary>
/// Set a bool value in the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <param name="value">Value to set or <c>null</c> to remove the property.</param>
protected void SetBool(string key, bool? value)
{
if (value.HasValue)
{
this.Items[key] = value.Value.ToString();
}
else
{
if (!this.Items.ContainsKey(key))
return;
this.Items.Remove(key);
}
} /// <summary>
/// Get a DateTimeOffset value from the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
protected DateTimeOffset? GetDateTimeOffset(string key)
{
string input;
DateTimeOffset result;
return this.Items.TryGetValue(key, out input) && DateTimeOffset.TryParseExact(input, "r", (IFormatProvider) CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out result) ? new DateTimeOffset?(result) : new DateTimeOffset?();
} /// <summary>
/// Set a DateTimeOffset value in the <see cref="P:Microsoft.AspNetCore.Authentication.AuthenticationProperties.Items" /> collection.
/// </summary>
/// <param name="key">Property key.</param>
/// <param name="value">Value to set or <c>null</c> to remove the property.</param>
protected void SetDateTimeOffset(string key, DateTimeOffset? value)
{
if (value.HasValue)
{
this.Items[key] = value.Value.ToString("r", (IFormatProvider) CultureInfo.InvariantCulture);
}
else
{
if (!this.Items.ContainsKey(key))
return;
this.Items.Remove(key);
}
}
}
IClaimsTransformation
public interface IClaimsTransformation
{
/// <summary>
/// Provides a central transformation point to change the specified principal.
/// Note: this will be run on each AuthenticateAsync call, so its safer to
/// return a new ClaimsPrincipal if your transformation is not idempotent.
/// </summary>
/// <param name="principal">The <see cref="T:System.Security.Claims.ClaimsPrincipal" /> to transform.</param>
/// <returns>The transformed principal.</returns>
Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
}
这个接口可以为传入的principal提供转换,适合全局为principal添加一些额外的claim。