上一篇文章(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.Claims
public 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          :10011
ClaimType :gender
ClaimValue :2
UserId :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响应了,这段就不展开讲了,就是一些基本操错,而加密过程和配置 密钥,后面会有单独的讲解章节

全文完 :)

本文已同步发表到我的segmentfault专栏 .net core web dev

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

ASP.NET Core Identity 实战(2)——注册、登录、Claim的更多相关文章

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

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

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

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

  3. ASP.NET Core Identity Hands On(2)——注册、登录、Claim

    上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...

  4. Asp.Net Core Identity 完成注册登录

    Identity是Asp.Net Core全新的一个用户管理系统,它是一个完善的全面的庞大的框架,提供的功能有: 创建.查询.更改.删除账户信息 验证和授权 密码重置 双重身份认证 支持扩展登录,如微 ...

  5. Asp.Net Core 项目实战之权限管理系统(5) 用户登录

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  6. Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  7. 【无私分享:ASP.NET CORE 项目实战(第八章)】读取配置文件(二) 读取自定义配置文件

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 我们在 读取配置文件(一) appsettings.json 中介绍了,如何读取appsettings.json. 但随之产生 ...

  8. IdentityServer(12)- 使用 ASP.NET Core Identity

    IdentityServer具有非常好的扩展性,其中用户及其数据(包括密码)部分你可以使用任何想要的数据库进行持久化. 如果需要一个新的用户数据库,那么ASP.NET Core Identity是你的 ...

  9. ASP.NET Core Identity Hands On(1)——Identity 初次体验

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

随机推荐

  1. Maven学习 三 Maven与Eclipse结合使用

    一检查是否已经集成了Maven 现在的Eclipse一般都是集成了Maven,如果确定集成开发工具是否集成了Maven, Windows->preferences->Maven,查看是否已 ...

  2. 我的idea突然没有SVN了是怎么回事

    总结一下没有svn选项的几种情况: 情况1:IntelliJ IDEA打开带SVN信息的项目不显示SVN信息,项目右键SVN以及图标还有Changes都不显示解决方法 在VCS菜单中有个开关,叫Ena ...

  3. Linux学习--- 宏定义下#、##的使用

    #   字符串化 ## 连接符号 eg: #include <stdio.h> #define ABC(x) #x #define DAY(c) myday##c int main (){ ...

  4. 使用itext生成pdf的,各种布局

    代码如下,jar包为itext.jar,itextAsia.jar,最好都是最新的 :2张图片也在最后贴出,把图片放到D盘可以直接生成制定格式的pdf. 最后生成的pdf如下: 代码如下: packa ...

  5. pb数据导出

    pb数据导出(一) 1.在窗口新建用户事件  ue_export    2.事件调用函数 gf_dw_to_excel(THIS.dw_dict) 3.写函数 :boolean lb_setborde ...

  6. zookeeper配置文件共享中心

    最近频繁的系统上线,每次打包都要把配置文件替换为正式环境的配置文件,虽然说就是复制粘贴的事,架不住文件杂乱,而且多. 期初的想法是有没有办法将配置文件与系统隔离开来,这样在更新时候,就只需要更新代码部 ...

  7. c++ 异常处理(2)

    前面一篇博文简单介绍了 c++ 异常处理的流程,但在一些细节上一带而过了,比如,_Unwind_RaiseException 是怎样重建函数现场的,Personality routine 是怎样清理栈 ...

  8. 5.html基础标签:块级+行级元素+特殊字符+嵌套规则

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. js内存空间的那点事

    由于js具有自动垃圾回收机制,导致接触js后一直没去关注js的内存分配及变量回收等原理,只是懵懂的了解用变量标记法(null)可以手动的去清除或是回收:是时候弥补这个大坑了... 垃圾回收两种方法 一 ...

  10. House of Spirit学习调试验证与实践

    作家:Bug制造机 原文来自:House of Spirit学习调试验证与实践 House of Spirit和其他的堆的利用手段有所不同.它是将存在的指针改写指向我们伪造的块(这个块可以位于堆.栈. ...