【ASP.NET Core】运行原理[3]:认证
本系列将分析ASP.NET Core运行原理
- 【ASP.NET Core】运行原理[1]:创建WebHost
- 【ASP.NET Core】运行原理[2]:启动WebHost
- 【ASP.NET Core】运行原理[3]:认证
本节将分析Authentication
源代码参考.NET Core 2.0.0
目录
- 认证
- AddAuthentication
- IAuthenticationService
- IAuthenticationHandlerProvider
- IAuthenticationSchemeProvider
 
- UseAuthentication
 
- AddAuthentication
- Authentication.Cookies
- 模拟一个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
【ASP.NET Core】运行原理[3]:认证的更多相关文章
- ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界
		HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ... 
- ASP.NET Core 运行原理解剖[5]:Authentication
		在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ... 
- ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)
		ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ... 
- ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行
		ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ... 
- ASP.NET Core 运行原理解剖[1]:Hosting
		ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ... 
- ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍
		在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ... 
- ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成
		在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ... 
- ASP.NET Core 运行原理剖析
		1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ... 
- ASP.NET Core 运行原理剖析 (转载)
		1.1. 概述 在ASP.NET Core之前,ASP.NET Framework应用程序由IIS加载.Web应用程序的入口点由InetMgr.exe创建并调用托管.以初始化过程中触发HttpAppl ... 
随机推荐
- 用户 'IIS APPPOOL\Private' 登录失败。
			用户 'IIS APPPOOL\Private' 登录失败. 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息. 异常详细 ... 
- 【Kafka源码】ReplicaManager启动过程
			在KafkaServer启动过程的入口中,会启动Replica Manager,众所周知,这是一个副本管理器.replica在Kafka中扮演的角色很重要,是保证消息不丢失的一个重要概念. repli ... 
- cocos2d导入iOS原生项目
			最近公司最新发下任务让融合一个cocos2dx写的游戏项目融合进现有项目,当看到要求时内心瞬间无数羊驼奔腾.------ 虽说内心是拒绝的,但是任务已经派发就必须要完成啊.所以在网上搜了大量的融入教程 ... 
- Oracle 数据库常用操作语句大全
			一.Oracle数据库操作 1.创建数据库 create database databasename 2.删除数据库 drop database dbname 3.备份数据库 完全 ... 
- Oracle的Recyclebin策略
			1.从oracle10g开始删除数据库表的时候并不是真正删除,而是放到了recyclebin中,这个过程类似 windows里面删除的文件会被临时放到回收站中. 2.删除的表系统会自动给他重命名就是你 ... 
- 《天书夜读:从汇编语言到windows内核编程》一 汇编指令与C语言
			1. Debug模式下,VC++6.0下断点运行,按CTRL+F11可查看汇编代码:另外可以用cl /c /FAs YourCppFile.cpp命令行在同目录生成YourCppFile.asm汇编文 ... 
- .net core 依赖注入扩展,实现随处控制反转
			在使用.net core时,依赖注入,主要使用通过构造函数注入.小编将通过扩展方式,实现在类中各个地方可以控制反转,获取实例. 1.首先自定义扩展类 using Microsoft.AspNetCor ... 
- 使用XML设计某大学主页站点地图--ASP.NET
			一.使用XML设计某大学主页站点地图步骤如下 1.创建一个空网站,在项目文件上右击,然后[添加新项],选择[站点地图],新建一个可默认为Web.sitemap的文件. 2.在Web.sitemap里修 ... 
- 以太网接口芯片W5300使用说明
			一.芯片简介 引用百度百科对芯片的一个简介,我就不再赘述. W5300的目标是在高性能的嵌入式领域,如多媒体数据流服务.与WIZnet现有的芯片方案相比较,W5300在内存空间和数据处理能力等方面都有 ... 
- 学习cordic算法所得(流水线结构、Verilog标准)
			最近学习cordic算法,并利用FPGA实现,在整个学习过程中,对cordic算法原理.FPGA中流水线设计.Verilog标准有了更加深刻的理解. 首先,cordic算法的基本思想是通过一系列固定的 ... 
