本系列将分析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. angular指令中@,=,&的区别

    当directive中的scope设置为一个对象的时候,该指令就有了一个独立的作用域,AngularJS提供了一种绑定策略用于隔离作用域和外部作用域进行通信. 1.@(or @attr) 使用@符号可 ...

  2. TIDB技术文档翻译

    http://blog.csdn.net/antony9118/article/details/60470115

  3. 2017 多校训练 1006 Function

    Function Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total ...

  4. Hawk-and-Chicken

    Hawk-and-Chicken Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) To ...

  5. Snail’s trouble

    Snail’s trouble Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...

  6. The Euler function(线性筛欧拉函数)

    /* 题意:(n)表示小于n与n互质的数有多少个,给你两个数a,b让你计算a+(a+1)+(a+2)+......+b; 初步思路:暴力搞一下,打表 #放弃:打了十几分钟没打完 #改进:欧拉函数:具体 ...

  7. How Many Answers Are Wrong

    How Many Answers Are Wrong Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/ ...

  8. File API简介

    File API让我们可以创建文件,存储在本地文件系统的一个安全沙箱里,亦可以从其他来源读取文件,并对其进行操作   Web应用通过requestFileSystem方法来访问本地文件系统,该方法是全 ...

  9. Java_String_01_由转义字符串得到其原本字符串

    在开发企业微信电子发票之拉取电子发票接口的时候,微信服务器会发送给我们一个2层的转义字符串,而我们要想得到我们想要的结果,就需要进行一些处理: 反转义+去除首尾双引号. 一.需求 现有一个字符串 st ...

  10. java 中 针对数组进行的工具类

    1.遍历数组的方法: public static void printfArray(int[] arr)  2. 获取数组中最大值: public static int getMax(int[] ar ...