AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

时间:2023-03-10 03:16:06
AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

文章目录

依赖注入

框架提供了三个依赖注入重载方法。

//注入认证服务
services.AddAuthentication(); //注入认证服务并制定默认架构名
services.AddAuthentication("Cookies"); //注入认证服务并设置配置项
services.AddAuthentication(config =>
{
});

看看注入代码

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);
}

AddAuthenticationCore注入了认证服务的核心对象。这个方法在Authentication.Core项目,这个项目定义了认证服务的核心对象,在Authentication.Abstractions项目中定义了核心接口。

AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

AddAuthenticationCore方法注入了IAuthenticationService,IClaimsTransformation,IAuthenticationHandlerProvider,IAuthenticationSchemeProvider

public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(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;
}

IAuthenticationService

认证服务,定义了五个方法

  • AuthenticateAsync: 认证
  • ChallengeAsync:挑战,校验认证
  • ForbidAsync:禁止认证
  • SignInAsync:登入
  • SignOutAsync:登出
classDiagram
class IAuthenticationService{
+AuthenticateAsync(HttpContext context, string scheme)
+ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
}

通过AuthenticateAsync方法源代码可以看到,AuthenticateService只是做了控制器的角色,校验schema,根据schema获取handler,主要的认证逻辑是由handler处理。其他的方法基本也是这样的逻辑。

 public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
if (scheme == null)
{
var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
scheme = defaultScheme?.Name;
if (scheme == null)
{
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
} var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null)
{
throw await CreateMissingHandlerException(scheme);
} var result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
{
var transformed = await Transform.TransformAsync(result.Principal);
return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
}
return result;
}

IClaimsTransformation

classDiagram
class IClaimsTransformation{
+TransformAsync(ClaimsPrincipal principal)
}

该接口只有一个方法,用于转换Claims。默认注入的NoopClaimsTransformation,不会做任何操作。如果需要对Claims做一些处理,实现IClaimsTransformation并覆盖注入就可以了。

public class NoopClaimsTransformation : IClaimsTransformation
{
/// <summary>
/// Returns the principal unchanged.
/// </summary>
/// <param name="principal">The user.</param>
/// <returns>The principal unchanged.</returns>
public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
return Task.FromResult(principal);
}
}

IAuthenticationHandlerProvider

classDiagram
class IAuthenticationHandlerProvider{
+GetHandlerAsync(HttpContext context, string authenticationScheme)
}

上面提到过handler处理了主要的认证业务逻辑,这个接口可以根据schema获取handler。

IAuthenticationSchemeProvider

classDiagram
class IAuthenticationSchemeProvider{
+GetAllSchemesAsync()
+GetSchemeAsync(string name)
+GetDefaultAuthenticateSchemeAsync()
+GetDefaultChallengeSchemeAsync()
+GetDefaultForbidSchemeAsync()
+GetDefaultSignInSchemeAsync()
+GetDefaultSignOutSchemeAsync()
+AddScheme(AuthenticationScheme scheme)
+RemoveScheme(string name)
+GetRequestHandlerSchemesAsync()
}

该接口主要定义了一些schema的操作方法。

AuthenticationScheme主要有三个属性,通过HandlerType与handler建立了关联。

classDiagram
class AuthenticationScheme{
Name
DisplayName
HandlerType
}

认证流程

graph TD
A(AuthenticationOptions定义五个认证动作的Schema)
A --> B1(Authenticate)
A --> B2(Challenge)
A --> B3(Forbid)
A --> B4(SignIn)
A --> B5(SingOut)
C(IAuthenticationSchemeProvider获取Schema)
B1 --> C
B2 --> C
B3 --> C
B4 --> C
B5 --> C
C --> D(IAuthenticationHandlerProvider获取Schema对应的Handler)
D --> E(处理请求)

其他

除了核心对象,还注入了用于数据保护和解码的辅助对象

services.AddDataProtection();
services.AddWebEncoders();

Authentication中间件

中间件会优先在容器中找IAuthenticationRequestHandler的实现,如果handler不为空的话,则执行handler的HandleRequestAsync方法。IAuthenticationRequestHandler通常在远程认证(如:OAuth, OIDC等)中使用。

如果没有IAuthenticationRequestHandler的实现,则会找默认schema,执行默认schema对应handler的AuthenticationAsync方法,认证成功后,给HttpContext的User对象赋值。

public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
}); // Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
} var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
context.User = result.Principal;
}
} await _next(context);
}