若是你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登录,那么你必定会疑惑 认证这个名词,这太正式了,这到底表明这什么?html
要想了解Identity中用户登陆以后,后续的访问时怎样识别用户的,那首先咱们得了解下认证(Authentication) 和受权(Authorization)的含义web
Authentication就是认证的意思,还举以前公园的例子,咱们拿到门票以后,去公园,入口门卫A首先要根据门票确认咱们是谁?是老王,仍是老赵,门票是否是真的,过时没。这个过程,这个识别来访者是谁的过程就叫作Authentication(身份认证过程)数据库
这两个单词太像了,甚至他们的释义都很像,以致于咱们在不了解他们的时候老是弄混他们,Authorization是受权的意思,上一小节中,门卫A识别出了咱们是谁?Ok,咱们是老李,那么门卫B能不能让咱们进园呢?不必定,门卫B还要再看,门票过时没,上一步已经看过门票是否过时了,为何还要看呢?事情是这样的:门卫A看到门票过时了,而后在门票副卡上写上“门票过时”四个大字,再写上“认证失败”,可是负责认证的门卫A没有拦着咱们,而是继续让咱们前进,由于动物园可能由于活动而容许过时门票进入,或者没有门票也能够,那么接下来受权的人门卫B来看门票,他根据动物园切实的状况看,看看许可范围,再问问后台这我的是否是动物园的管理员。通过种种校验,发现,虽然门票过时了,可是今天是开放日,没门票也行,可是咱们是普通游客,却来到了管理员通道,因此没让咱们进园——受权失败json
好了,这就是认证和受权(Authentication & Authorization),两个不一样的事,由两个不一样的人(或者组件)来作cookie
如今,咱们要记住认证与受权中的一个要点session
认证只肯定用户是谁即便认证失败,也不会拦截用户访问,拦截用户访问发生在受权阶段mvc
另外要注意的是 Authentication和Authorization并不属于Identity的一部分,都不属于Identity,它和Identity是相互独立的,而后一块儿协做。也就是说,即使咱们没有使用Identity ,咱们有咱们本身的用户存储,角色等等和身份权限相关的一切,那么咱们能够将咱们的成员系统完美的与Asp.Net Core 进行集成,咱们能够假设,Identity就是咱们写的,而后将其与Asp.Net Core进行集成app
那么为何要将这个与Identity无关的认证过程Authentication放在这里呢?由于它们是协做的 Authentication和Authorization本事就是要与成员系统协做的,在代码上,他们解耦而且独立,可是在事实逻辑上,成员系统和认证受权老是一块儿使用的,因此一块儿讲容易理解asp.net
那么这篇文章只讲 Authentication与Authorization中的第一个 —— Authentication,先来了解一下,asp.net core 是怎样知道咱们已经登录的访客是谁的async
中间件(Middleware)讲起来又是一个长长的故事,若是你彻底没概念,那么我建议你先简单学习一下asp.net core 中的中间件,你只要知道它的运行原理便可
在通常的asp.net core web 项目中,咱们通常把身份认证中间件放在 静态文件中间件以后,Mvc中间件以前
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(routes => { //略... }); }
这样作的目的很简单,仅对须要认证的部分作认证
在http请求到达 mvc中间件以前,也就是进入咱们写的逻辑代码以前,身份认证就结束了,也就是说,身份认证不能在 controller action中控制
咱们用一张图来简化发生在身份认证中间件中的认证过程,注意,这里立刻要引入一个新的概念
中间件是嵌在中间件管道中的一个一个模块,http请求有条件的流经他们
另外一个类似的东西,有不少 handler 嵌在身份认证中间件上,那么http是流经全部的handler吗?
Authentication Hander 顾名思义,他就是切实处理身份认证的组件,它附加在 authentication middleware 上,在请求到来时, middleware 会在全部附加在它之上的handler中选取一个用来作身份认证
那么当咱们使用Identity时,哪些 authentication handler 被附加了呢?当请求到来时,authentication middleware 如何知道要选择哪一个handler呢?
接下来,咱们一一解答
Identity只添加了一种类型的 handler ——CookieAuthenticationHandler
在咱们的StartUp
类中的ConfigureServices
方法中,咱们添加了Identity的Service
services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();
但事情没这么简单,在添加Identity的同时,Identity还未咱们的项目添加了AuthenticationService
和CookieAuthenticationHandler
public static IdentityBuilder AddIdentity<TUser, TRole>( { // Services used by identity services.AddAuthentication(options => { options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; // 略... }) .AddCookie(IdentityConstants.ApplicationScheme, o => { // 略...
在services.AddAuthentication
的内部添加了AuthenticationService
namespace Microsoft.Extensions.DependencyInjection { public static class AuthenticationCoreServiceCollectionExtensions { public static IServiceCollection AddAuthenticationCore(this IServiceCollection services) { services.TryAddScoped<IAuthenticationService, AuthenticationService>(); services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>(); services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
注意上面代码的最后三行,后面涉及到他们的获取,他们就是在此处添加的
namespace Microsoft.Extensions.DependencyInjection { public static class CookieExtensions { 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);
上方的代码添加了 CookieAuthenticationHandler
在添加Authentication service的同时,制定默认的 authentication scheme(这个概念在以前的文章中提到过,若是你还有印象的话) 是谁(就是下方的cookie authentication handler)
这时候另外一个问题浮现了,只添加了一个 cookie authentication handler,为何还要将他制定成默认值,是否有有点画蛇添足呢?
虽然Identity只添加了一种类型的 handler(cookie authentication handler),可是他同时添加了多个
在 authentication 中间件上,区分各个handler的方法是指定不一样的 authentication scheme,而不是经过 handler 的类型
其实它添加了这么多:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddCookie(IdentityConstants.ApplicationScheme, 略) .AddCookie(IdentityConstants.ExternalScheme, 略) .AddCookie(IdentityConstants.TwoFactorRememberMeScheme, 略) .AddCookie(IdentityConstants.TwoFactorUserIdScheme,略);
不过咱们暂时不用关心这些是什么
目前为止咱们已经知道了这样几件事:
添加Identity时,Identity添加了用于身份认证的服务,以及默认激活的用于认证的handler——CookieAuthenticationHandler
因此用户登陆后再次访问的时候,会经过cookie来识别当前用户是谁
这一小节中咱们先编写测试代码,来看看认证过程产生了哪些结果
建立一个名为TestAuthController
的控制器,代码大体以下:
namespace IdentityInAction.Controllers { public class TestAuthController : Controller { public IActionResult Index() { return Json(new { User.Identity.IsAuthenticated, User.Identity.AuthenticationType, Claims=User.Claims.Select(c => new { c.Type, c.Value }) // 略...
这些代码将返回一个json字符串,内容是 authentication的部分结果和用户的claims信息,这三行核心代码的意思分别是:
运行程序,不要进行登录,若是已经登录了则退出登录,退出登录的连接是右上角的LogOut
而后访问http://localhost:{你的端口}/testauth/index
,获得的结果以下:
{ "isAuthenticated": false, "authenticationType": null, "claims": [] }
因为没有用户登录,因此结果里几乎什么都没有,而后再尝试登录后再次访问这个地址,结果以下:
{ "isAuthenticated": true, "authenticationType": "Identity.Application", "claims": [ { "type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "value": "78a032c7-0d67-4cec-b031-2d15a7bac755" }, { "type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "value": "abc@abc.com" }, { "type": "AspNet.Identity.SecurityStamp", "value": "babbb46b-6ba0-4b87-875a-92088197dfbf" } ] }
"isAuthenticated": true
这表明认证成功
"authenticationType": "Identity.Application"
这是对该请求进行认证的handler的名字,由前文咱们知道,咱们默认的handler是名为IdentityConstants.ApplicationScheme
的cookie handler,咱们看一小段源代码证明一下:
public class IdentityConstants { private static readonly string CookiePrefix = "Identity"; public static readonly string ApplicationScheme = CookiePrefix + ".Application";
正如所料,接下来就是claims了,再上篇文章中提到再登录过程当中加入到Identity的claims有这些:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
AspNet.Identity.SecurityStamp
这些claims随着票据一块儿加密写到了cookie中,如今他们又随着cookie一块儿传了回来
要注意的是,即使咱们退出登录后没有身份认证失败了,可是咱们仍然得到了这个Uri的访问权限,缘由在于认证并不阻止用户,受权才会阻止用户,而咱们没又作受权方面的限制
看到这里,咱们的认证过程的大致已经清楚了,接下来咱们要看下整个认证过程的一点细节,整个过程是自上而下的,看标题为工做的那一列
工做 | 注释 |
---|---|
获取IAuthenticationHandlerProvider的实例 | |
获取默认的AuthenticationScheme | |
使用上一步的scheme获取IAuthenticationService实例 | |
上一步的service经过第一步的IAuthenticationHandlerProvider获取handler | handler 是 cookie authentication handler |
handler 调用 AuthenticateAsync,这个方法最终调用了handler的HandleAuthenticateAsync① | 这个方法是事实上执行认证的方法 |
获取 CookieAuthenticationOptions.Cookie.Name指定的存储票据的cookie的原始字符串 | 这个Name的默认值是`.AspNetCore.Identity.Application` |
解密cookie字符串得到AuthenticationTicket的实例 | |
检查是否使用了session存储,若是有则验证是否存在对应的session | |
检查cookie 是否过时 | |
检查是否须要刷新cookie | |
建立新的AuthenticationTicket | |
将AuthenticationTicket中的Principal设置到HttpContext.User上,认证结束 | 在动手作一节中,咱们使用的User就是在这个时候被赋值的 |
Identity的实现比较复杂,兜兜转转最终的验证时由 cookie authentication handler 的 HandleAuthenticateAsync
完成的,若是你在看Identity源代码的话,那么直接跳转到这里能够节省时间
事实上,说的简单一点,就是在登录的时候,把票据加密写到cookie里,验证的时候
获取cookie >解密 >还原成票据 >把票据塞到http context中
即便是认证失败了,也是这4个步骤,最终 负责受权的组件会检查 http context 中的票据,还会结合其它状况来肯定是否容许当前的请求继续进行下去,而咱们的逻辑代码中也能够查看票据,根据不一样的 认证结果 返回不一样的数据
最后咱们贴上AuthenticationService的AuthenticateAsync源代码,这写代码就是上方表格所对应的代码,可是咱们要注意到,表格中的内容还包含这份代码所调用的其它代码的步骤
public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme) { if (scheme == null) { var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync(); scheme = defaultScheme?.Name; if (scheme == null) { throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found."); } } var handler = await Handlers.GetHandlerAsync(context, scheme); if (handler == null) { throw await CreateMissingHandlerException(scheme); } var result = await handler.AuthenticateAsync(); if (result != null && result.Succeeded) { var transformed = await Transform.TransformAsync(result.Principal); return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme)); } return result; }
全文完