如果你没接触过旧版 Asp.Net Mvc 中的 Authorize 或者 Cookie 登陆, 那么你一定会疑惑 认证这个名词, 这太正式了, 这到底代表这什么?
获取资源之前得先过两道关卡 Authentication & Authorization
要想了解 Identity 中用户登录之后, 后续的访问时怎样识别用户的, 那首先我们得了解下认证 (Authentication) 和授权(Authorization) 的含义
Authentication
Authentication 就是认证的意思, 还举之前公园的例子, 我们拿到门票之后, 去公园, 入口门卫 A 首先要根据门票确认我们是谁? 是老王, 还是老赵, 门票是不是真的, 过期没. 这个过程, 这个识别来访者是谁的过程就叫做 Authentication(身份认证过程)
那 Authorization 又是啥?
这两个单词太像了, 甚至他们的释义都很像, 以至于我们在不了解他们的时候总是弄混他们, Authorization 是授权的意思, 上一小节中, 门卫 A 识别出了我们是谁? Ok, 我们是老李, 那么门卫 B 能不能让我们进园呢? 不一定, 门卫 B 还要再看, 门票过期没, 上一步已经看过门票是否过期了, 为什么还要看呢? 事情是这样的: 门卫 A 看到门票过期了, 然后在门票副卡上写上 "门票过期" 四个大字, 再写上 "认证失败", 但是负责认证的门卫 A 没有拦着我们, 而是继续让我们前进, 因为动物园可能因为活动而允许过期门票进入, 或者没有门票也可以, 那么接下来授权的人门卫 B 来看门票, 他根据动物园切实的情况看, 看看许可范围, 再问问后台这个人是不是动物园的管理员. 经过种种校验, 发现, 虽然门票过期了, 但是今天是开放日, 没门票也行, 但是我们是普通游客, 却来到了管理员通道, 所以没让我们进园 -- 授权失败
小结
好了, 这就是认证和授权 (Authentication & Authorization), 两个不同的事, 由两个不同的人(或者组件) 来做
认证用来确认来者是谁, 确认身份(确认之后可能没有身份)
授权用来确认持有此身份的来者能不能访问当前请求的资源
现在, 我们要记住认证与授权中的一个要点
认证只确定用户是谁即使认证失败, 也不会拦截用户访问, 拦截用户访问发生在授权阶段
另外要注意的是 Authentication 和 Authorization 并不属于 Identity 的一部分, 都不属于 Identity, 它和 Identity 是相互独立的, 然后一起协作. 也就是说, 即便我们没有使用 Identity , 我们有我们自己的用户存储, 角色等等和身份权限相关的一切, 那么我们可以将我们的成员系统完美的与 Asp.Net Core 进行集成, 我们可以假设, Identity 就是我们写的, 然后将其与 Asp.Net Core 进行集成
那么为什么要将这个与 Identity 无关的认证过程 Authentication 放在这里呢? 因为它们是协作的 Authentication 和 Authorization 本事就是要与成员系统协作的, 在代码上, 他们解耦并且独立, 但是在事实逻辑上, 成员系统和认证授权总是一起使用的, 所以一起讲容易理解
那么这篇文章只讲 Authentication 与 Authorization 中的第一个 -- Authentication, 先来了解一下, asp.net core 是怎样知道我们已经登陆的访客是谁的
身份认证中间件 Authentication Middleware
中间件 (Middleware) 讲起来又是一个长长的故事, 如果你完全没概念, 那么我建议你先简单学习一下 asp.net core 中的中间件 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.0&tabs=aspnetcore2x , 你只要知道它的运行原理即可
在一般的 asp.net core web 项目中, 我们一般把身份认证中间件放在 静态文件中间件之后, Mvc 中间件之前
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseStaticFiles();
- app.UseAuthentication();
- app.UseMvc(routes =>
- {
- // 略...
- });
- }
这样做的目的很简单, 仅对需要认证的部分做认证
在 http 请求到达 mvc 中间件之前, 也就是进入我们写的逻辑代码之前, 身份认证就结束了, 也就是说, 身份认证不能在 controller action 中控制
我们用一张图来简化发生在身份认证中间件中的认证过程, 注意, 这里马上要引入一个新的概念
身份认证 handler
中间件是嵌在中间件管道中的一个一个模块, http 请求有条件的流经他们
另一个相似的东西, 有很多 handler 嵌在身份认证中间件上, 那么 http 是流经所有的 handler 吗?
Authentication Handler
Authentication Hander 顾名思义, 他就是切实处理身份认证的组件, 它附加在 authentication middleware 上, 在请求到来时, middleware 会在所有附加在它之上的 handler 中选取一个用来做身份认证
那么当我们使用 Identity 时, 哪些 authentication handler 被附加了呢? 当请求到来时, authentication middleware 如何知道要选择哪个 handler 呢?
接下来, 我们一一解答
Cookie Authentication 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
Identity 的身份认证基于 cookie (上篇文章我们了解到 Identity 在登陆时将票据加密写入了 cookie)
所以用户登录后再次访问的时候, 会通过 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 信息, 这三行核心代码的意思分别是:
用户是否已经通过身份认证
对次请求进行认证的 handler 的名称(在上篇文章中我们有提到 authentication scheme 就是 authentication type 的另一个名字, 记住这件事对我们的理解很有帮助)
这个用户的 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 有这些:
- UserName |
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
- UserId|
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
- SecurityStamp(如果支持的话)|
- AspNet.Identity.SecurityStamp
存储在数据库中的额外 Claims(如果支持的话)(注: 支持, 但当前用户没有)
这些 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 中的票据, 还会结合其它情况来确定是否允许当前的请求继续进行下去, 而我们的逻辑代码中也可以查看票据, 根据不同的 认证结果 返回不同的数据
全文完
来源: https://www.cnblogs.com/rocketRobin/p/9105720.html