[聚合文章] 【ASP.NET Core】运行原理[3]:认证

.Net 2017-12-14 41 阅读

目录

  1. 认证
    1. AddAuthentication
      1. IAuthenticationService
      2. IAuthenticationHandlerProvider
      3. IAuthenticationSchemeProvider
    2. UseAuthentication
  2. Authentication.Cookies
  3. 模拟一个Cookie认证

认证

认证已经是当前Web必不可缺的组件。看看ASP.NET Core如何定义和实现认证。
在Startup类中,使用认证组件非常简单。

public void ConfigureServices(IServiceCollection services){    services.AddAuthentication();}public void Configure(IApplicationBuilder app, IHostingEnvironment env){    app.UseAuthentication();}

AddAuthentication

先来分析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);}

IAuthenticationService

在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:通知用户需要登录。在默认实现类AuthenticationHandler中,返回401。
ForbidAsync:通知用户权限不足。在默认实现类AuthenticationHandler中,返回403。
SignInAsync:登录用户。(该方法需要与AuthenticateAsync配合验证逻辑)
SignOutAsync:退出登录。

而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会先查DefaultAuthenticateScheme,如果为null,再查DefaultScheme
实际上,AuthenticationService的其他方法都是这样的模式,最终调用的都是handle的同名方法。

IAuthenticationHandlerProvider

因此,我们看看获取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{}

之所以接口拆分,应该是考虑到大部分的系统的登录和退出是单独一个身份系统处理。

IAuthenticationSchemeProvider

通过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。

UseAuthentication

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代码中,我们看到先查询出所有的AuthenticationRequestHandler。如果存在,则立即调用其HandleRequestAsync方法,成功则直接返回。
(RequestHandler一般是处理第三方认证响应的OAuth / OIDC等远程认证方案。)
如果不存在RequestHandler或执行失败,将调用默认的AuthenticateHandle的AuthenticateAsync方法。同时会对context.User赋值。

Authentication.Cookies

Cookies认证是最常用的一种方式,这里我们分析一下Cookie源码:

AddCookie

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

AddCookie(this AuthenticationBuilder builder, Action<CookieAuthenticationOptions> configureOptions)可能是我们最常用的
该方法将注册CookieAuthenticationHandler用于处理认证相关。

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(CookieAuthenticationDefaults.AuthenticationScheme);        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

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。