本系列将分析 ASP.NET Core 运行原理
本节将分析 Authentication
源代码参考. NET Core 2.0.0
认证已经是当前 Web 必不可缺的组件。看看 ASP.NET Core 如何定义和实现认证。
在 Startup 类中,使用认证组件非常简单。
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthentication();
- }
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseAuthentication();
- }
先来分析 AddAuthentication:
- public static IServiceCollection AddAuthenticationCore(this IServiceCollection services) {
- services.TryAddScoped < IAuthenticationService,
- AuthenticationService > ();
- services.TryAddScoped < IAuthenticationHandlerProvider,
- AuthenticationHandlerProvider > ();
- services.TryAddSingleton < IAuthenticationSchemeProvider,
- AuthenticationSchemeProvider > ();
- return services;
- }
- public static AuthenticationBuilder AddAuthentication(this IServiceCollection services) {
- services.AddAuthenticationCore();
- return new AuthenticationBuilder(services);
- }
在 AddAuthentication 方法中注册了 IAuthenticationService、IAuthenticationHandlerProvider、IAuthenticationSchemeProvider3 个服务。
首先分析下
:
- IAuthenticationService
- public interface IAuthenticationService {
- Task < AuthenticateResult > AuthenticateAsync(HttpContext context, string scheme);
- Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
- Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
- Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
- Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
- }
AuthenticateAsync:验证用户身份,并返回 AuthenticateResult 对象。
ChallengeAsync:通知用户需要登录。在默认实现类
中,返回 401。 ForbidAsync:通知用户权限不足。在默认实现类
- AuthenticationHandler
中,返回 403。 SignInAsync:登录用户。(该方法需要与 AuthenticateAsync 配合验证逻辑) SignOutAsync:退出登录。
- AuthenticationHandler
而 IAuthenticationService 的默认实现类为:
- public class AuthenticationService : IAuthenticationService
- {
- public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
- {
- if (scheme == null)
- {
- var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
- scheme = defaultScheme?.Name;
- }
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- var result = await handler.AuthenticateAsync();
- if (result != null && result.Succeeded)
- return AuthenticateResult.Success(new AuthenticationTicket(result.Principal, result.Properties, result.Ticket.AuthenticationScheme));
- return result;
- }
- }
在 AuthenticateAsync 代码中,先查询 Scheme,然后根据 SchemeName 查询 Handle,再调用 handle 的同名方法。
解释一下
会先查
- GetDefaultAuthenticateSchemeAsync
,如果为 null,再查
- DefaultAuthenticateScheme
。 实际上,AuthenticationService 的其他方法都是这样的模式,最终调用的都是 handle 的同名方法。
- DefaultScheme
因此,我们看看获取 Handle 的
:
- IAuthenticationHandlerProvider
- public interface IAuthenticationHandlerProvider
- {
- Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme);
- }
该接口只有一个方法,根据 schemeName 查找 Handle:
- public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
- {
- public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
- {
- Schemes = schemes;
- }
- public IAuthenticationSchemeProvider Schemes { get; }
- public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
- {
- if (_handlerMap.ContainsKey(authenticationScheme))
- return _handlerMap[authenticationScheme];
- var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
- if (scheme == null)
- return null;
- var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
- ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType)) as IAuthenticationHandler;
- if (handler != null)
- {
- await handler.InitializeAsync(scheme, context);
- _handlerMap[authenticationScheme] = handler;
- }
- return handler;
- }
- }
在 GetHandlerAsync 方法中,我们看到是先从 IAuthenticationSchemeProvider 中根据 schemeName 获取 scheme,然后通过 scheme 的 HandleType 来创建 IAuthenticationHandler。
创建 Handle 的时候,是先从 ServiceProvider 中获取,如果不存在则通过 ActivatorUtilities 创建。
获取到 Handle 后,将调用一次 handle 的 InitializeAsync 方法。
当下次获取 Handle 的时候,将直接从缓存中获取。
需要补充说明的是一共有 3 个 Handle:
IAuthenticationHandler、IAuthenticationSignInHandler、IAuthenticationSignOutHandler。
- public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler, IAuthenticationHandler{}
- public interface IAuthenticationSignOutHandler : IAuthenticationHandler{}
- public interface IAuthenticationHandler{}
之所以接口拆分,应该是考虑到大部分的系统的登录和退出是单独一个身份系统处理。
通过 IAuthenticationHandlerProvider 代码,我们发现最终还是需要 IAuthenticationSchemeProvider 来提供 Handle 类型:
这里展示 IAuthenticationSchemeProvider 接口核心的 2 个方法。
- public interface IAuthenticationSchemeProvider
- {
- void AddScheme(AuthenticationScheme scheme);
- Task<AuthenticationScheme> GetSchemeAsync(string name);
- }
默认实现类
:
- AuthenticationSchemeProvider
- public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
- {
- private IDictionary<string, AuthenticationScheme> _map = new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal);
- public virtual void AddScheme(AuthenticationScheme scheme)
- {
- if (_map.ContainsKey(scheme.Name))
- {
- throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
- }
- lock (_lock)
- {
- if (_map.ContainsKey(scheme.Name))
- {
- throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
- }
- _map[scheme.Name] = scheme;
- }
- }
- public virtual Task<AuthenticationScheme> GetSchemeAsync(string name)
- => Task.FromResult(_map.ContainsKey(name) ? _map[name] : null);
- }
因此,整个认证逻辑最终都回到了 Scheme 位置。也就说明要认证,则必须先注册 Scheme。
AddAuthentication 实现了注册 Handle,UseAuthentication 则是使用 Handle 去认证。
- public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
- {
- return app.UseMiddleware<AuthenticationMiddleware>();
- }
使用了
:
- AuthenticationMiddleware
- public class AuthenticationMiddleware
- {
- private readonly RequestDelegate _next;
- public IAuthenticationSchemeProvider Schemes { get; set; }
- public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
- {
- _next = next;
- Schemes = schemes;
- }
- public async Task Invoke(HttpContext context)
- {
- 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);
- }
- }
在 Invoke 代码中,我们看到先查询出所有的
。如果存在,则立即调用其 HandleRequestAsync 方法,并返回。
- AuthenticationRequestHandler
的 AuthenticateAsync 方法。同时会对 context.User 赋值。
- AuthenticateHandle
Cookies 认证是最常用的一种方式,这里我们分析一下 Cookie 源码:
- public static class CookieExtensions
- {
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder)
- => builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme)
- => builder.AddCookie(authenticationScheme, configureOptions: null);
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action<CookieAuthenticationOptions> configureOptions)
- => builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions);
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, Action<CookieAuthenticationOptions> configureOptions)
- => builder.AddCookie(authenticationScheme, displayName: null, configureOptions: configureOptions);
- public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
- {
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
- return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
- }
- }
可能是我们最常用的 该方法将注册 CookieAuthenticationHandler 用于处理认证相关。
- AddCookie(this AuthenticationBuilder builder, Action<CookieAuthenticationOptions> configureOptions)
- public class CookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>,IAuthenticationSignInHandler
- {
- public async virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
- {
- var signInContext = new CookieSigningInContext(
- Context,
- Scheme,
- Options,
- user,
- properties,
- cookieOptions);
- var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name);
- var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
- Options.CookieManager.AppendResponseCookie(
- Context,
- Options.Cookie.Name,
- cookieValue,
- signInContext.CookieOptions);
- }
- protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
- {
- var cookie = Options.CookieManager.GetRequestCookie(Context, Options.Cookie.Name);
- var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());
- return AuthenticateResult.Success(ticket);
- }
- }
这里我们用 Cookie 示例:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthentication(options => options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => options.Cookie.Path = "/");
- }
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.Map("/login", app2 => app2.Run(async context =>
- {
- var claimIdentity = new ClaimsIdentity();
- claimIdentity.AddClaim(new Claim(ClaimTypes.Name, Guid.NewGuid().ToString("N")));
- await context.SignInAsync(new ClaimsPrincipal(claimIdentity));
- }));
- app.UseAuthentication();
- app.Run(context => context.Response.WriteAsync(context.User?.Identity?.IsAuthenticated ?? false ? context.User.Identity.Name : "No Login!"));
- }
当访问 login 的时候,将返回 Cookie。再访问除了 login 以外的页面时则返回一个 guid。
- public class DemoHandle : IAuthenticationSignInHandler
- {
- private HttpContext _context;
- private AuthenticationScheme _authenticationScheme;
- private string _cookieName = "user";
- public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
- {
- _context = context;
- _authenticationScheme = scheme;
- return Task.CompletedTask;
- }
- public Task<AuthenticateResult> AuthenticateAsync()
- {
- var cookie = _context.Request.Cookies[_cookieName];
- if (string.IsNullOrEmpty(cookie))
- {
- return Task.FromResult(AuthenticateResult.NoResult());
- }
- var identity = new ClaimsIdentity(_authenticationScheme.Name);
- identity.AddClaim(new Claim(ClaimTypes.Name, cookie));
- var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), _authenticationScheme.Name);
- return Task.FromResult(AuthenticateResult.Success(ticket));
- }
- public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
- {
- _context.Response.Cookies.Append(_cookieName, user.Identity.Name);
- return Task.CompletedTask;
- }
- }
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthentication(options =>
- {
- options.DefaultScheme = "cookie";
- options.AddScheme<DemoHandle>("cookie", null);
- });
- }
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.Map("/login", app2 => app2.Run(async context =>
- {
- var claimIdentity = new ClaimsIdentity();
- claimIdentity.AddClaim(new Claim(ClaimTypes.Name, Guid.NewGuid().ToString("N")));
- await context.SignInAsync(new ClaimsPrincipal(claimIdentity));
- context.Response.Redirect("/");
- }));
- app.UseAuthentication();
- app.Run(context => context.Response.WriteAsync(context.User?.Identity?.IsAuthenticated ?? false ? context.User.Identity.Name : "No Login!"));
- }
默认访问根目录的时候,显示 "No Login"
当用户访问 login 路径的时候,会跳转到根目录,并显示登录成功。
这里稍微补充一下 Identity.IsAuthenticated => !string.IsNullOrEmpty(_authenticationType);
本文链接: http://www.cnblogs.com/neverc/p/8037477.html
来源: http://www.cnblogs.com/neverc/p/8037477.html