本系列将分析ASP.NET Core运行原理

本节将分析Authentication

源代码参考.NET Core 2.0.0

目录

  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

【ASP.NET Core】运行原理[3]:认证的更多相关文章

  1. ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

    HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...

  2. ASP.NET Core 运行原理解剖[5]:Authentication

    在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...

  3. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  4. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  5. ASP.NET Core 运行原理解剖[1]:Hosting

    ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...

  6. ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

    在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...

  7. ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

    在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...

  8. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  9. ASP.NET Core 运行原理剖析 (转载)

    1.1. 概述 在ASP.NET Core之前,ASP.NET Framework应用程序由IIS加载.Web应用程序的入口点由InetMgr.exe创建并调用托管.以初始化过程中触发HttpAppl ...

随机推荐

  1. nodejs运行前端项目

    有时候我们会创建一些小项目,只有几个简单html,没有引入一些前端框架,也没有使用webpack,那我们要如何让代码在我们本地跑起来呢? 当然是有很多种方法,IIS.wampserver等等好多都可以 ...

  2. JS深层继承

    我们在书写JS的时候常常被一种现象困扰 let jsonA = { a1: { b1:1; }, }; let jsonB = jsonA; jsonB.a1.b1 = 2; console.log( ...

  3. 磁盘管理 之 parted命令添加swap,文件系统

    第1章 磁盘管理 1.1 必须要了解的. 1.1.1 ps aux 命令中 RSS 与VSZ的含义 rss 进程占用的物理内存的大小 单位:kb : vsz 进程占用的虚拟的内存大小(物理内存+swa ...

  4. HQL语法

    HQL:Hibernate Query Language HQL是完全面向对象的查询语言,因此可以支持继承和多态等特征. $下面介绍HQL语句的语法 1.from子句 from Person 表明从P ...

  5. 用git上传本地文件到github

    1.在自己的github账号下新建仓库--------得到github仓库地址 2.本地安装git---在将要克隆的文件夹下 右击点击Git Bash Here 3.输入命令 $ git clone ...

  6. Nginx详解以及LNMP的搭建

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  7. Javascript 内核Bug

    Javascript 内核Bug: js 执行(9.9+19.8)加法运算 等于 29.700000000000003) <html> <head> <title> ...

  8. Java实现邮箱发送验证码

    第一步,导入JAR包,JAR包下载地址[http://pan.baidu.com/s/1kVRvGyF] 正式代码: 首先书写一个工具类: MailUtil import javax.mail.*; ...

  9. 如何编写通用的 Helper Class

    Github: https://github.com/nzbin/snack-helper Docs: https://nzbin.github.io/snack-helper 前言 什么是 help ...

  10. C#.NET 用程序画图,曲线图

    using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security ...