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

上一篇文章(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

洛谷 动态规划一日游 P2577、P1070、P2051

记 2018年3月19日 贼颓呢&#xff0c;一天就写了两道DP&#xff0c;还都不会写&#xff0c;这可GG。 动态规划真的难且有趣&#xff0c;算法题中动态规划占到了很大的比例&#xff0c;而且动态规划往往是辅助解决一些其他类型问题的基础&#xff0c;加深加强对动态规划问题的…

【DP】【高精】幸运票 (jzoj 2122)

幸运票 题目大意&#xff1a; 一个长度为2N的序列&#xff0c;这些数的总和为S&#xff0c;当这个序列的前N个和后N个总和相等时&#xff0c;它是符合题意的&#xff0c;问有符合题意的有多少种可能 样例输入 2 2 样例输出 4 数据范围限制 1<N<50 S<1000 解…

51nod-有限背包计数问题【dp】

正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId1597 题目大意 nnn种物品&#xff0c;第iii个大小为iii且有iii个。 求恰好填满大小为nnn的背包的方案数 解题思路 我们可以将背包分为两份&#xff0c;对于大小小于等于n\sqrt nn​的物品&#xff0c;这…

Build 2018大会:.NET概述和路线图

在Microsoft Build 2018大会上&#xff0c;.NET项目管理主管Scott Hunter和.NET社区主管Scott Hanselman举行了一场有关.NET未来发展的会谈。会谈指出&#xff0c;未来.NET平台将可以搭建任何类型的应用程序&#xff1a;桌面程序、web程序、云程序、移动应用、游戏应用、物联网…

洛谷P2278操作系统 模拟+堆

一道模拟题 竟然活生生的不会模拟&#xff0c;感觉自己好菜啊。 在模拟的时候&#xff0c;一定要弄清楚要对什么进行模拟。 题解 进程的等待队列是一个优先队列&#xff0c;优先队列是以优先级降序作为第一关键字&#xff0c;以进入时间为第二关键字。在操作系统这道题目中…

【二分】抄书 (jzoj 2123)

抄书 题目大意&#xff1a; 有n本书&#xff0c;分给m个人抄&#xff0c;每个人只能拿到连续的书&#xff08;不能把一本书分开&#xff09;&#xff0c;问抄书最多的人要抄多少页 样例输入 9 3 100 200 300 400 500 600 700 800 900 样例输出 1700 数据范围限制 对于…

nssl1467-U【差分】

正题 题目大意 n∗nn*nn∗n的矩阵&#xff0c;每次让一个下三角形内数字加上一定权值。求最后所有位置的异或和 解题思路 我们发现如果我们对于没行做前缀和的话&#xff0c;我们需要修改的位置就是一个竖直下去的一列和斜着的一条&#xff0c;所以我们可以分别对于竖着的和斜…

汽车之家汽车品牌Logo信息抓取 DotnetSpider实战[三]

一、正题前的唠叨第一篇实战博客&#xff0c;阅读量1000&#xff0c;第二篇&#xff0c;阅读量200&#xff0c;两篇文章相差近5倍&#xff0c;这个差异真的令我很费劲&#xff0c;截止今天&#xff0c;我一直在思考为什么会有这么大的差距&#xff0c;是因为干货变少了&#xf…

洛谷P1801 黑匣子 双堆套路的使用

题意 题目链接 题解 这道题本可以用Treap暴力求解出来&#xff0c;但是不够优雅&#xff0c;因为没有充分利用到题目中给的条件&#xff0c;那就是要求的ithith小的值的ii是单调递增的。我们用两个堆来维护,大顶堆和小顶堆。 大顶堆中的元素是排好序的前i&#x2212;1&qu…

2019.01.26【NOIP普及组】模拟赛C组总结

总结 这次比赛的得分是&#xff1a;10001060170 第一题想了一会&#xff0c;想到了方法&#xff0c;直接打出来&#xff0c;第二题不会&#xff0c;想水分&#xff0c;但没水到&#xff0c;第三题打了一个假的DP&#xff0c;10分&#xff0c;第四题用DP超时了&#xff0c;60分…

nssl1468-V【状压,数学期望,dfs】

正题 题目大意 nnn个球排成一排颜色不同&#xff0c;每次选择一个随机的[1..n][1..n][1..n]中的xxx&#xff0c;然后删掉第xxx个或第n−x1n-x1n−x1个数&#xff0c;求删kkk次之后删掉的白球最多&#xff0c;求删掉数量的期望值 解题思路 考虑状态压缩dpdpdp&#xff0c;定义第…

洛谷 一种堆套路 P1631序列合并、P2085最小函数值

题目链接 序列合并 最小函数值 题解 这两道题做法基本一样&#xff0c;是使用同一种套路解决的&#xff0c;这里用序列合并来举例说明。 序列合并要求出N2N2个和中最小的N个数。 我们用一个堆来维护我们需要的数&#xff0c;并且保证当前最小值一定在堆中。 把a和b排个序…

.NET Core 2.1 正式发布

这次更新包括对性能的改进&#xff0c;对运行时和工具的改进。还包含一种以 NuGet 包的形式部署工具的新方法。我们添加了一个名为 Span<T> 的新基元类型&#xff0c;它可以在没有内存分配的情况下对数据进行操作。还有许多其他新的 API&#xff0c;专注于密码学&#xf…

纪中培训总结(2019年1月21~31日)

Day 0&#xff08;21号&#xff09; 中午从家里出发&#xff0c;坐了两个小时的车&#xff08;堵得要命&#xff09;&#xff0c;过了虎门大桥&#xff0c;在一个服务站吃起了晚餐&#xff08;麦当劳的包&#xff09;&#xff0c;又坐了一个小时的车&#xff0c;终于到了&…

nssl1469-W【dp】

正题 题目大意 nnn个点的一棵树&#xff0c;每条边一个权值为0或1和一个目标权值&#xff08;0或1或者没有限制&#xff09;。每次可以将一个路径上的权值取反&#xff0c;求最小翻转数量和最小翻转路径长度。 解题思路 首先我们可以从序列的类似问题上知道一条边不会被翻转超…

[翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能

原文: Comparing AWS Lambda performance of Node.js, Python, Java, C# and GoAWS 最近宣布他们支持了 C&#xff03; (Net Core 2.0 版本) 和 Go 语言来实现 Lambda 功能。(译者注: AWS Lambda 是 AWS 推出的 Serverless 功能&#xff0c;请参阅这里或 Serverless 相关资料)做…

codeforces gym-101745 C-Infinite Graph Game 分块

题意 题目链接 给出一个顶点带权无向图。 定义访问操作&#xff1a;访问一个点&#xff0c;就要把与这个点相邻的点的权值全部都加到答案里去&#xff0c;然后给这个顶点的权值/2。现在给出一个无穷的访问序列中的一个循环节&#xff0c;求最终答案的极限是多少。 注意&…

P5579-[PA2015]Siano【线段树】

正题 题目链接:https://www.luogu.com.cn/problem/P5579 题目大意 nnn个树&#xff0c;第iii个每天长高aia_iai​米。 mmm次修剪&#xff0c;第iii次在did_idi​天&#xff0c;将高度为bib_ibi​的部分修剪掉 求每次修剪掉的高度 解题思路 按照aia_iai​排序后我们知道每次修…

【结论】立体井字棋(jzoj 2124)

立体井字棋 题目大意&#xff1a; 在一个nnn的正方体中&#xff0c;由n个格子连成一条直线的方案数&#xff08;多少种可能用n个格子连成一条直线&#xff09; 样例输入 2 样例输出 28 数据范围限制 对于30%的数据&#xff0c; n<10&#xff1b; 对于100%的数据&am…

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

如果你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登陆&#xff0c;那么你一定会疑惑 认证这个名词&#xff0c;这太正式了&#xff0c;这到底代表这什么&#xff1f;获取资源之前得先过两道关卡Authentication & Authorization要想了解Identity中用户登录之后&…