ASP.NET Core Identity是用于构建ASP.NET Core Web应用程序的 成员资格系统,包括 成员资格、登陆和用户数据存储
这是来自于 ASP.NET Core Identity 仓库主页的官方介绍,若是你是个萌新你可能不太理解什么是成员资格,那我来解释一下,成员资格由 membership 直译而来, membership 还有会员资格、会员身份、会员全体等相关含义,咱们能够将其简单直接但并不是十分恰当的理解为用户管理系统html
ASP.NET Core Identity(下文简称Identity),既然能够理解为用户管理系统,那么她天然是十分强大的,包含用户管理的方方面面,简单的来说包括:mysql
Ok Identity这么好,她到底长啥样?我怎么用呢,接下来咱们先来作一个小小的demo体验一下,一边作,一边讲解web
打开Vs的建立新项目面板依次选择 .net core -> asp.net core web 应用程序sql
选择 web 应用程序(模型视图控制器)->更改身份认证->我的用户帐户
在这以后默认会使用 sqlserver compact来存储用户数据数据库
按 Ctrl+F5
运行项目json
注意到右上角的 register 和 login了吗?在咱们选择我的身份认证的时候 Identity被自动添加到项目中,而且生成了segmentfault
AccountController
注册和登录相关的代码都在这里) ManageController
这是给注册用户用的,主要有两个功能,改密码和双因子验证) 点击 register 进入注册界面,界面看起来还不错,甚至能够直接使用,而后咱们注册一个帐户安全
当你点击 register 按钮以后,会跳转到 数据库迁移(若是你用过EF Core,那么这个概念你并不会感到陌生) 确认页面微信
应用迁移后,你要等一会刷新页面,在这段时间里,我建议你看看迁移页面上的信息
若是看不太懂,那么请看下图cookie
Ok, 迁移好了以后,就会回到主页,右上角的注册登陆会变成你的邮箱和注销连接,点击你的帐户邮箱,先看看里面有什么
这个页面里的内容就在ManageController
中,若是你不知道双因子认证Two-factor authentication是什么,不要紧,在后续讲到它时再说
点一下 Send verification email
连接,不用担忧,不会真的发送邮件
Identity 提供了电子邮件验证功能,就是一般见到的那种,邮件中会有一个加密的连接,用于验证邮件,如何生成连接Identity已经作好了,甚至写了邮件发送的接口——IEmailSender
和一个空的实现EmailSender
namespace IdentityDemo.Services { public interface IEmailSender { Task SendEmailAsync(string email, string subject, string message); } }
刚刚咱们注册了一个新的用户,那么用户存哪里了?默认存储用的是sqlserver compact,接下来咱们找到它,再看看Identity是如何设计用户数据,另外我本身粗浅的认为学习一个新技术最好就是先看看它把数据存成什么样了
在Vs上方的菜单里依次选择 工具->链接到数据库
Ok,默认数据库的位置是哪里?数据库叫什么名字呢?如今,先关闭这个窗口,打开项目根目录下的appsettings.json
配置文件
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentityDemo-E3266F7D-D9FD-4038-9AF7-773A31FC3680;Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } } }
能够看到咱们数据库的名字叫作aspnet-IdentityDemo-E3266F7D-D9FD-4038-9AF7-773A31FC3680
,而他的位置在 C:\Users\{当前登陆的用户名}\
下面。 再操做一次,而后点 继续 选择你的数据库文件
这可能会遇到数据库文件占用的的状况
这是由于刚刚启动的程序没有退出,若是你用的是自托管启动,那么关闭它若是用的是IISExpress,也关闭它
好了,先看看数据库里有什么吧
_EFMigrationsHistory
是 Ef的迁移历史表没必要关注此表
AspNetUserClaims
、AspNetRoleClaims
是用户和角色的声明表,以前咱们提到 Identity 是基于声明的认证模式(Claims Based Authentication)的,Claim在其中扮演者很重要的角色,甚至角色(Role)都被转换成了Claim,Claim相关会在后面专门讲解,若是你不了解它,不要着急
AspNetUsers
、AspNetRoles
和AspNetUserRoles
存储用户和角色信息
AspNetUserTokens
、AspNetUserLogins
存储的是用户使用的外部登录提供商的信息和Token,外部登录提供商指的是像微博、QQ、微信、Google、微软这类提供 oauth 或者 openid connect 登录的厂商。好比 segmentfault 就可使用微博登录
接下来就要解释下最为重要的一张表AspNetUsers
刚刚注册的用户的切实数据以下
Id AccessFailedCount ConcurrencyStamp Email EmailConfirmed LockoutEnabled LockoutEnd NormalizedEmail NormalizedUserName PasswordHash PhoneNumber PhoneNumberConfirmed SecurityStamp TwoFactorEnabled UserName ------------------------------------ ----------------- ------------------------------------ ----------------- -------------- -------------- ---------- ----------------- ------------------ ------------------------------------------------------------------------------------ ----------- -------------------- ------------------------------------ ---------------- ----------------- d4929072-e704-447c-a9aa-e1b7f510fd37 0 5765da8f-1945-40c6-8f81-97604739e5ec xxxxxxxx@163.com 0 1 NULL XXXXXXXX@163.COM XXXXXXXX@163.COM AQAAAAEAACcQAAAAEHQ+3Z9h0tiUsinNPs8B99skAqbXh0zcWlGWTgTVik6S85viEWQFV8TF8bRyDTW8rw== NULL 0 a4d9c858-cc08-4ceb-8d5d-92a6cb1c40b8 0 xxxxxxxx@163.com
主键 默认是 nvarchar(450) 但事实上是存储的Guid字符串,另外值得一提的是Id的建立时机
主键的Guid是在建立用户时在构造函数中生成的
namespace Microsoft.AspNetCore.Identity { public class IdentityUser : IdentityUser<string> { public IdentityUser() { Id = Guid.NewGuid().ToString(); }
这是一小段源代码,用来证实上述内容
也就是说它是彻底随机的无序Guid,那么它可能带来的隐患就是当用户量很是大的时候,建立用户可能变慢,不过对于绝大多数情景来说,这不太可能(有那么多的用户),固然这可能发生,因此在后续的文章里,我会讲解如何使用bigint做为主键
这个是用来记录用户尝试登录却登录失败的次数,咱们能够经过这个来肯定在何时须要锁定用户,
同步标记,每当用户记录被更改时必需要更改此列的值,事实上存储的是Guid,而且在建立用户模型的时候直接在属性上初始化随机值
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
另外要注意,这个列的值的更改时机,它是在程序中手动编写的代码更改的,而不是由数据库更改(多是考虑到并非全部ef支持的数据库都支持timestamp 或者 rowversion 类型)
Email就是Email,NormalizedEmail是 规范化后的Email
什么是规范化呢?
在咱们刚刚建立的用户中,能够看到 NormalizedEmail 只是将email 的值变成大写了,我想你已经有点明白了
的确,这样会提升数据库的查询效率,从Identity的代码中能够看到,关于Email的查询都转换成了对 NormalizedEmail的查询。空口无凭,咱们看一小段简短的代码
namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { public override Task<TUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) { // 略... return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); }
NormalizedEmail
在使用时你能够不用关心,你也不要去手动更改它的值,由于当用户建立或者用户资料更新的时候 NormalizedEmail
都会被自动更新
而后咱们依旧看一眼源代码代码
namespace Microsoft.AspNetCore.Identity { public class UserManager<TUser> : IDisposable where TUser : class { public virtual async Task<IdentityResult> CreateAsync(TUser user) { // 略... await UpdateNormalizedUserNameAsync(user); await UpdateNormalizedEmailAsync(user); return await Store.CreateAsync(user, CancellationToken); } protected virtual async Task<IdentityResult> UpdateUserAsync(TUser user) { // 略... await UpdateNormalizedUserNameAsync(user); await UpdateNormalizedEmailAsync(user); return await Store.UpdateAsync(user, CancellationToken); }
UserName就是UserName NormalizedUserName 仍是规范化以后的UserName,也就是转换到大写
它们的行为和上述的 Email、NormalizedEmail 一致,就不赘述了
邮件已经确认,这是个bit(bool)类型的列,前文提到Identity含有发送和验证确认邮件的功能,在建立用户的时候这个值默认是false ,确认连接由 Identity生成,以后交由 IEmailSender发送。Ok,这里一半任务就作完了,展现一小段代码能让你更清楚这个过程
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null) { //略... 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); 略...
这些代码是建立项目时生成的,是属于你的项目而不是Identity的
你可能想到,在注册以后咱们顺利的进入系统,而并无被阻止,即使咱们没有确认过邮件,数据库中的数据也指明邮件没有确认
的确,由于这已经不在Identity的范畴内了,这属于咱们的程序逻辑,要不要阻止未验证邮件的用户登陆,须要咱们本身作,不过,很简单,只需在登录时多写几行代码而已,这里暂时先不展开讨论
他们的数据类型是 bit和datetimeoffset(7),LockoutEnabled指示这个用户可不能够被锁定,LockoutEnd指定锁定的到期日期,null 或者一个过去的时间,表明这个用户没有被锁定
须要注意的是Identity为咱们实现了锁定功能的基础设施,可是是否在用户锁定以后禁止用户登陆是属于咱们程序的逻辑的
密码哈希,Identity使用的hash 强度是比较高的,暴力破解的难度十分大
======================= HASHED PASSWORD FORMATS ======================= Version 2: PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. (See also: SDL crypto guidelines v5.1, Part III) Format: { 0x00, salt, subkey } Version 3: PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } (All UInt32s are stored big-endian.)
version 2和3 是为了兼容Identity V1 V2 和V3 他们的对应关系以下
安全标记,一个随机值,在用户凭据相关的内容更改时,必须更改此项的值,事实存储的是Guid
它的更改时机有:
同ConcurrencyStamp
同样,SecurityStamp
也是在程序中由代码控制更改的
电话和电话已确认,比较容易理解
指示当前用户是否开启了双因子验证
初次体验到此结束 :)
本文已同步发表到个人博客园博客
ASP.NET Core Identity Hands On(1)——Identity 初次体验