本系列将分析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. .7-Vue源码之AST(3)

    上一节到了parseHTML函数,该函数接受一个字符串与一个对象,字符串即对应的DOM,对象包含几个字符串匹配集及3个长函数. 简略梳理部分函数代码如下: // Line-7672 function ...

  2. Codeforces 376A. Night at the Museum

    A. Night at the Museum time limit per test 1 second memory limit per test 256 megabytes input standa ...

  3. git使用教程之git分支

    1 分支简介 让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流. 你将经历如下步骤: 开发某个网站. 为实现某个新的需求,创建一个分支. 在这个分支上开展工作. 正在此 ...

  4. 学习笔记TF060:图像语音结合,看图说话

    斯坦福大学人工智能实验室李飞飞教授,实现人工智能3要素:语法(syntax).语义(semantics).推理(inference).语言.视觉.通过语法(语言语法解析.视觉三维结构解析)和语义(语言 ...

  5. js和jquery实现监听键盘事件

    一.使用javascript实现 <!DOCTYPE html> <html> <head> <meta charset="utf-8"& ...

  6. [置顶]【实用 .NET Core开发系列】- 导航篇

    前言 此系列从出发点来看,是 上个系列的续篇, 上个系列因为后面工作的原因,后面几篇没有写完,后来.NET Core出来之后,注意力就转移到了.NET Core上,所以再也就没有继续下去,此是原因之一 ...

  7. main之前初始化流程

    main之前初始化流程 本文分别介绍Keil调用的ARMCC以及ARM-NONE-EABI-GCC两个编译器在main之前的操作: Keil MDK启动文件 总结一下MDK的启动流程: 1.系统初始化 ...

  8. 教你五步制作精美的HTML时钟

    学了一段时间的HTML.CSS和JS后,给大家做一款漂亮的不像实力派的HTML时钟,先看图:涉及到的知识点有: CSS3动画.DOM操作.定时器.圆点坐标的计算(好多人是不是已经还给自己的老师了~)  ...

  9. Robotium 框架学习之Class By

    Class By定义了页面元素的定位和支持哪些页面元素(至少我是这么理解的),使用及其简单:Used in conjunction with the web methods. Examples are ...

  10. Java提高十五:容器元素比较Comparable&Comparator深入分析

    我们经常用容器来存放元素,通常而言我们是不关系容器中的元素是否有序,但有些场景可能要求容器中的元素是有序的,这个时候用ArrayList  LinkedList  Hashtable HashMap ...