上一篇文章(ASP.NET Core Identity Hands On(1)——Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将一起学习Identity 默认生成的样板代码的注册与登陆过程

注册/Register

打开AccountController找到 public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)方法

这个方法切实的创建用户并存储到数据库,完整的过程代码比较复杂,所以我们用一张表格来展现具体过程,首先看紧挨着箭头的那一列文本,即标题为“工作”的那一列,这是完整的顺序过程,用户创建即从头走到尾。剩余的信息是帮助理解的,因为在Register方法中,并没有展现关键的内容,我列举出他们出现的位置,这样有助于理解

在看图片之前,我们先看一下CreateAsync代码,这可能和你的有点不同,因为我删除了一点无关紧要的东西来减少篇幅

namespace IdentityDemo.Controllers

{

public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)

{

if (ModelState.IsValid)

{

var user = new ApplicationUser { UserName = model.Email, Email = model.Email };

var result = await _userManager.CreateAsync(user, model.Password);

if (result.Succeeded)

{

var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);

var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);

await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);

await _signInManager.SignInAsync(user, isPersistent: false);

return RedirectToLocal(returnUrl);

}

AddErrors(result);

}

// If we got this far, something failed, redisplay form

return View(model);

}

如果不太理解代码也没关系,我们看表格

另外值得注意的是图中的标注①,验证用户名中的字符,他的默认值是

public string AllowedUserNameCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";

如果我们想更改设置怎么办?还有表格中提到了 如果用户支持锁定如果要求邮件不能重复,这些未确定的值从哪来的?

如果你熟悉 asp.net core ,那我猜你可能已经想到了

没错 Options 就是 Di中的 Options在起作用。

打开项目根目录的Startup.cs文件

public class Startup{    //略...public void ConfigureServices(IServiceCollection services)    {        //略...services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();        //略...}
}

当前整个identity options应用的都是默认配置,所以这里看不到option的踪迹,接下来我们就以刚才提到的三个选项为例,修改option 的值,修改后的代码如下

public class Startup{    //略...public void ConfigureServices(IServiceCollection services)    {        //略...services.AddIdentity<ApplicationUser, IdentityRole>(options=>{options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.@";options.User.RequireUniqueEmail = false;options.Lockout.AllowedForNewUsers = false;}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();        //略...}
}

允许的用户名字符由abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+变为abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.@ (现在你再试试注册,之前可以用 _ 现在不能用了)

要求邮件不重复由true变为false

允许新用户锁定由true变为false

IdentityOptions 可配置的选项非常多,完整的列表请移步 配置 ASP.NET 核心标识

更多关于Options的内容请移步 asp.net core 文档——配置与选项 一节

登陆之前——咱们得先弄清Claim

举个例子

假设有这样一家动物园,这家动物园要门票,门票要从动物园门口的售票室买,购买后,能得到一张纸质的票据。纸很特殊,动物园验票能通过纸张来判断门票是不是真的,还能看出你有没有涂改门票。门票上还有时间,指示什么时候门票到期,只要门票没有到期,你就可以随意进出动物园

嗯,这么长个例子,其实和Claim没什么关系 :)

门票上有什么?我们来假设一下

好了,我们假设的门票就这样,从门票的第二行(姓名...)开始,每一行都是一个Claim

有了上面的铺垫,我们接下来正式介绍下Claim

释义

Claim 本意有

  • vt.声称;索取;断言;需要

  • vi.提出要求

  • n.索赔;声称;(根据权利而提出的)要求;断言

断言是比较准确的释义,另外可以理解成声明,每一条claim 都代表了一条票据的信息,比如示例票据上的姓名等等。claim 的基本组成是 typevalue,上面票据中左侧的就是type右面就是value

在 .net core 基础类库中是含有Claim的实现类的,它的位置是

System.Security.Claims.Claim

我们看一个真实的claim的例子

{"sub": "1234567890","name": "John Doe","iat": 1516239022}

这个例子中含有3个claim

  • sub subject 主题,往往指Id

  • name 就是name

  • iat issue at 发出时间

这个例子中的 type 都是 JWT RFC中的标准jwt claim,上面这个例子是一个jwt票据的一部分,而在identity 中,默认使用的是cookie 身份认证,所以使用的不是 jwt 票据,而是加密cookie票据(identity没有这样定义,这样写是为了和jwt票据区分开),但是票据里面的内容,jwt和 加密cookie都是一样的都是——“claim

再回顾下 claim是什么? 就是一条一条的 type-value 键值对,里面存储了身份证明信息

而承载claim的东西就是票据,票据有很多种 jwt 和cookie 都是主流,不过应用场景不一样,by the way 票据的英文名称是“token”,你需要记住它,后续的文章中,我们会学习如何同时使用支持移动后端验证(jwt token)以及仅仅使用 jwt token

登陆过程

依旧在AccountController中,我们找到public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)方法

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)

{

var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);

if (result.Succeeded)

{

return RedirectToLocal(returnUrl);

}

if (result.RequiresTwoFactor)

{

return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });

}

if (result.IsLockedOut)

{

return RedirectToAction(nameof(Lockout));

}

else

{

ModelState.AddModelError(string.Empty, "Invalid login attempt.");

return View(model);

}

}

这是个简略版本的代码,只保留了关键信息

用于登陆的代码只有一行var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);但里面做的事情可是非常多的,我们稍后在讲,现在我们先要了解一下,登陆之后有哪些结果产生——result

SignInResult

SignInResult 只有5个属性

  • Success 表示一切顺利,登陆成功

  • Failed 登陆失败

  • LockedOut 用户被锁定了

  • NotAllowed 不允许登陆

  • TwoFactorRequired 要求双因子验证

然后我们看一下具体的登陆过程,这里仍旧是一个表格,

登陆过程描述

代码范围 作用
我们的代码 从用户输入获取用户名、密码、记住我
Identity 检查是否需要确认邮件以及此用户邮件是否已经确认
检查是否支持锁定用户以及此用户是否已被锁定
检查用户密码是否正确,以及是否需要升级①
如果支持锁定用户,并且支持在登陆失败超过指定次数锁定用户则增加AccessFailedCount计数,并且在到达设置的计数上限后清零计数设置LockoutEnd时间②
通过用户的基本信息生成Claims 及ClaimsIdentity③
如果支持额外的Claims存储则添加额外的Claims④
【注:Identity支持,额外的Claims存储在AspNetUserClaims表中】
生成ClaimsPrinciple⑤
添加认证方法Claim⑥
HttpAbstractions 确保上一个单元格中的认证方法不是空
通过认证方法,获取指定的IAuthenticationSignInHandler实例⑦
Security 使用ClaimsPrinciple创建 票据
加密票据
将加密后的票据添加到http响应的cookie头中

上表就是登陆过程,Identity默认使用cookie作为 claims 的载体,在最后的步骤中将含有claims的票据加密存储到cookie中,这样在登陆之后再次访问就可以验证cookie来识别当前是否有用户登录,以及登陆用户的身份

代码范围一列中,我们看到有4列,这和注册过程中相比,多出了 HttpAbstractions 和 Security,我们先来解释下这两个东西是什么

HttpAbstractions*

这是 asp.net core 中的http基础相关抽象,例如HttpRequest、HttpResponse、HttpContext等等
关于 HttpAbstractions的更多信息,可以访问它的GitHub主页 https://github.com/aspnet/HttpAbstractions

Security*

这个库里面主要包含用于web开发的安全与授权相关的中间件,在上表中 的标注⑦IAuthenticationSignInHandler的实例,事实上就是CookieAuthenticationHandler,在后续的文章里当我么讲到身份认证过程的时候会详细讲述身份认证中间件及handler是如何工作的

另外,还可以访问他的GitHub主页获得更多信息https://github.com/aspnet/Security

接下来我们解释一下上表中的标注

标注解释

①检查用户密码是否正确,以及是否需要升级

在ASP.NET Core Identity Hands On(1)——Identity 初次体验 中,我们有提到 Identity的密码哈希有两个版本 v2和v3,那么如果一个旧的Identity升级到新的Identity那么密码会不兼容,所以在Identity中密码验证为了兼容旧版,做了一些特殊处理。v3的密码byte以0x01开头,而v2以0x00开头,从这里可以判断出密码哈希是哪个版本的然后根据不同的版本来验证密码,密码验证有3个结果——失败、成功、成功且需要更新版本:

namespace Microsoft.AspNetCore.Identity{    public enum PasswordVerificationResult{Failed = 0,Success = 1,SuccessRehashNeeded = 2略...

当验证结果是SuccessRehashNeeded时,就会重新计算新的密码Hash存入数据库,从而完成密码的兼容升级

②AccessFailedCount计数、LockoutEnd时间

ASP.NET Core Identity Hands On(1)——Identity 初次体验中有讲解

Claim、IIdentity+ClaimsIdentity、IPrincipal+ClaimsPrincipal

在过去的asp.net mvc 以及现在的新的 asp.net mvc core中,HttpContext都有个User属性,可能很多开发者都没有使用过它

namespace Microsoft.AspNetCore.Http{  

     public abstract class HttpContext{               public abstract ClaimsPrincipal User { get; set; }        

所以,你暂时将ClaimsPrincipal理解成User就可以,而ClaimsPrincipal中有两个重要的属性

namespace System.Security.Claimspublic class ClaimsPrincipal : IPrincipal{             public virtual IEnumerable<ClaimsIdentity> Identities { get; }   

        public virtual IIdentity Identity { get; }

Identities是这个Principal(user)拥有的所有Identity,Identity 是这个Principal(user)拥有的最重要的Identity,而这个Identity的实际类型是ClaimsIdentity,这里就相当于Principal是用户,而Identity是用户的身份证,身份证里面记录的是这个用户的个人信息,也就是claims

namespace System.Security.Claims{     public class ClaimsIdentity : IIdentity{               public virtual IEnumerable<Claim> Claims { get; }

再看一下上面的三小段代码,你应该就能理解 Principal、Identity、Claim的关系了

③通过用户的基本信息生成Claims 及ClaimsIdentity

在这个步骤中大部分claims都被加入到 ClaimsIdentity中,如下所示(|右侧是该claim的type)

  • 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(如果支持的话)

这里的 claim 的type 是url,还有字符串,而之前提到的都是缩写,这是不是很令人疑惑呢?

原因是 并没有什么规定type是什么的标准,我们也可以自定义type,type的意义在于发放票据的一方和验证票据的一方知道是什么意思就可以了,所以,如上

④额外的claims 以及 AspNetUserClaims 表

现在我们 就来解析一下我们的第二张表 AspNetUserClaims

这张表相对就比较简单,这张表就是用于存储额外的属于用的claim的

其中Id是int类型,这有别于User表中Id是varchar(450)要注意一下

我们来假设一个场景

假设我们的网站有一个特殊的设置,就是在用户是男性的时候,显示一个短发logo是女性时显示一个长发logo,我们有很多方法实现,如果用claim实现的话就是相对简单的,我们将性别的的type定义为 gender, value定义为 1、2,那么在用户创建时或者创建后,为用户创建一条claim数据,假设用户是女性:

Id          :10011ClaimType   :genderClaimValue  :2UserId      :071d2a6e-ac2e-4db6-8941-372a3991b912q

当这位用户登录时,就会将这条数据加入到cookie票据中,成为其中的一条claim,而在用户后续的访问中,我们直接从cookie中拿到票据,并看到票据上写了,这为用户是一位女性,然后为其显示一个长发logo

⑤生成ClaimPrincipal

这是一个一步的操作

CalimsIdentity id = await GenerateClaimsAsync(user);return new ClaimsPrincipal(id);

就像我们把A用户的身份证交到了A的手中,然后把A交还给了调用方,这很好理解

⑥添加认证方法Claim

Principal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));

这一步是将使用的认证方法添加到了 Identity中,它的type 是

http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod

不过登陆过程中,这个值是null,所以他没有真的添加到Identity中

⑥ 和⑦

在表格中我们能看到⑥ 和⑦的范围已经不再Identity里了,所以Identity的任务已经结束了,Identity就把用户Principal做好,身份证Identity做好,身份证上的信息Claim填好,就结束了。接下来选择哪个用于用户登录的handler,handler怎么做才能让用户登录,Identity就不知道了,因为Identity是成员系统,而用户登录属于web框架,举一个反例,不用Identity就不能使用cookie登陆了吗?答案显然不是的,所以成员系统知道用户是谁,将用户信息做成一个票据,交给web框架

离开 Identity之后第一件事就是确保上一个单元格中的认证方法不是空,可是刚刚明明说了,它是null

没错当它是null 的时候,会去寻找默认的authentication schema(这是认证方法的另一个名字),在startup 类中,注册Identity的服务时,Identity还注册了cookie authentication handler 顺便还添加了 默认的 authentication scheme 我们看一个精简版的代码片段

public static IdentityBuilder AddIdentity<TUser, TRole>(略...)
{services.AddAuthentication(options =>{        // 略...options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;}).AddCookie(IdentityConstants.ApplicationScheme, o =>{        // 略...})

ApplicationScheme的切实的默认值是Identity.Application,如果你不太能理解这一小节的内容,没关系,你只需要知道表格中做了什么事就可以,关于 身份认证 authentication 是个不算简单的过程,后续会撰文专门讲解

最后就是加密和将cookie写入http响应了,这段就不展开讲了,就是一些基本操错,而加密过程和配置 密钥,后面会有单独的讲解章节

原文地址:

https://www.cnblogs.com/rocketRobin/p/9077523.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

ASP.NET Core Identity Hands On(2)——注册、登录、Claim相关推荐

  1. ASP.NET Core Identity 实战(4)授权过程

    这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...

  2. ASP.NET Core Identity 实战(1)——Identity 初次体验

    ASP.NET Core Identity是用于构建ASP.NET Core Web应用程序的成员资格系统,包括成员资格.登录和用户数据存储 这是来自于 ASP.NET Core Identity 仓 ...

  3. ASP.NET Core Identity自定义数据库结构和完全使用Dapper而非EntityFramework Core

    前言 原本本节内容是不存在的,出于有几个人问到了我:我想使用ASP.NET Core Identity,但是我又不想使用默认生成的数据库表,想自定义一套,我想要使用ASP.NE Core Identi ...

  4. IdentityServer4【QuickStart】之使用asp.net core Identity

    使用asp.net core Identity IdentityServer灵活的设计中有一部分是可以将你的用户和他们的数据保存到数据库中的.如果你以一个新的用户数据库开始,那么,asp.net co ...

  5. ASP.NET Core Identity 实战(3)认证过程

    如果你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登陆,那么你一定会疑惑 认证这个名词,这太正式了,这到底代表这什么? 获取资源之前得先过两道关卡Authentica ...

  6. Asp.Net Core WebApi 身份验证、注册、用户管理

    Asp.Net Core WebApi身份验证.注册.用户管理 用户服务 用户实体.用户模型及数据上下文 应用程序设置文件 在Startup.cs中配置身份验证 参考文献 用了两天的时间研究了在Asp ...

  7. ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系

    ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...

  8. ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

    ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core Identity 迁移数据 - ASP.NET C ...

  9. ASP.NET Core gRPC 使用 Consul 服务注册发现

    一. 前言 gRPC 在当前最常见的应用就是在微服务场景中,所以不可避免的会有服务注册与发现问题,我们使用gRPC实现的服务可以使用 Consul 或者 etcd 作为服务注册与发现中心,本文主要介绍 ...

最新文章

  1. 让编译器对一些警告闭嘴
  2. 第2章 熟悉Eclipse开发工具---- System.out.println(sum=+(a+b));
  3. ALEIDoc EDI(5)--Inbound Function
  4. 【转载】MongoDB 1000W级数据 Insert和Query和Delete性能测试
  5. MySQL入门之创建、更新、修改、复制、查看表
  6. PTA c语言 求幂级数展开的部分和
  7. 文件上传 文件大小和类型
  8. 机器视觉光源学习总结——条形光源
  9. 转 markdown编写规则、语法
  10. AForge.net库类下载方式
  11. LX4056耐高压线性锂电池充电IC(耐压30V,带OVP,带NTC)
  12. Matlab入门教程 | 001 球的体积问题
  13. 从硬件到软件,亲自动手组建家庭多媒体文件系统
  14. Codeforces469div2F curfew(贪心)
  15. C语言编程>第二十三周 ② 请补充fun函数,该函数的功能是:交换数组a中最大和最小两个元素的位置,结果重新保存在原数组中,其它元素位置不变。注意数组a中没有相同元素。
  16. 凤凰x86 卡android,卡ANDROIDx86_64的解决办法
  17. Java对象扁平化的操作
  18. 滚烫出炉:06中国IT十大财经人物
  19. 神经网络算法的基本原理,神经网络是机器算法吗
  20. 利用photoshop cs5新增功能之“智能填充

热门文章

  1. 点击回退按钮刷新页面
  2. 《图解 HTTP》读书笔记(未完待续)
  3. libco协程库源码解读
  4. 启动物联网智能校园,NFC技术可变出哪些创新应用?
  5. 基于Qt的光盘刻录开发
  6. 【项目 报错】 项目启动,内存溢出 可能出现的各种报错
  7. java泛型不是计算运行时的数据类型
  8. [每日一题] 11gOCP 1z0-052 :2013-09-23 Oracle11g 内存参数设置...................................C7...
  9. LVS负载均衡-NET、DR模式配置
  10. 大话领域驱动设计——简介