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

这是来自于 ASP.NET Core Identity 仓库主页的官方介绍,如果你是个萌新你可能不太理解什么是成员资格,那我来解释一下,成员资格由 membership 直译而来, membership 还有会员资格、会员身份、会员全体等相关含义,我们可以将其简单直接但并非十分恰当的理解为用户管理系统

ASP.NET Core Identity(下文简称Identity),既然可以理解为用户管理系统,那么她自然是十分强大的,包含用户管理的方方面面,简单的来讲包括:

  1. 用户数据存储(使用任意你喜欢的关系型数据库,从sqllite到mysql、sqlserver等等,由Entity Framwork 支持)
  2. 登陆、注册外加身份认证(基于cookie的身份认证,如果你使用Vs那么还可以生成用于注册登录的用户界面及处理代码)
  3. 角色管理
  4. 基于声明的认证模式Claims Based Authentication(如果你不知道Claim是什么,没关系你先记住这个单词)

Ok Identity这么好,她到底长啥样?我怎么用呢,接下来我们先来做一个小小的demo体验一下,一边做,一边讲解

软件准备

  • Visual Studio 2017(越新越好,如果你没有的话就下载Vs2017社区版,安装很快速,与旧版本兼容,完全免费传送门

动手做

打开Vs的创建新项目面板依次选择 .net core -> asp.net core web 应用程序

选择 web 应用程序(模型视图控制器)->更改身份认证->个人用户账户

在这之后默认会使用 sqlserver compact来存储用户数据

Ctrl+F5运行项目

注意到右上角的 register 和 login了吗?在我们选择个人身份认证的时候 Identity被自动添加到项目中,并且生成了

  • 账户控制器AccountController 注册和登陆相关的代码都在这里)
  • 登陆注册页面(还有其它的 如:确认邮件、访问受限等等)
  • 管理控制器ManageController 这是给注册用户用的,主要有两个功能,改密码和双因子验证)
  • Identity可不会给你生成管理员界面哦

点击 register 进入注册界面,界面看起来还不错,甚至可以直接使用,然后我们注册一个账户

当你点击 register 按钮之后,会跳转到 数据库迁移(如果你用过EF Core,那么这个概念你并不会感到陌生) 确认页面

应用迁移后,你要等一会刷新页面,在这段时间里,我建议你看看迁移页面上的信息

如果看不太懂,那么请看下图

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的迁移历史表不必关注此表

AspNetUserClaimsAspNetRoleClaims是用户和角色的声明表,之前我们提到 Identity 是基于声明的认证模式(Claims Based Authentication)的,Claim在其中扮演者很重要的角色,甚至角色(Role)都被转换成了Claim,Claim相关会在后面专门讲解,如果你不了解它,不要着急

AspNetUsersAspNetRolesAspNetUserRoles存储用户和角色信息

AspNetUserTokensAspNetUserLogins存储的是用户使用的外部登陆提供商的信息和Token,外部登陆提供商指的是像微博、QQ、微信、Google、微软这类提供 oauth 或者 openid connect 登陆的厂商。比如 segmentfault 就可以使用微博登陆

接下来就要解释下最为重要的一张表AspNetUsers

用户数据核心存储—— AspNetUsers

刚刚注册的用户的切实数据如下

博客园不支持横向滚动代码,所以我把代码都竖过来了,看着有点别扭 或者你可以到这里看具体的数据

Id
------------------------------------
d4929072-e704-447c-a9aa-e1b7f510fd37 AccessFailedCount
-----------------
0 ConcurrencyStamp
------------------------------------
5765da8f-1945-40c6-8f81-97604739e5ec Email
-----------------
xxxxxxxx@163.com EmailConfirmed
--------------
0 LockoutEnabled
--------------
1 LockoutEnd
----------
NULL NormalizedEmail
-----------------
XXXXXXXX@163.COM NormalizedUserName
------------------
XXXXXXXX@163.COM PasswordHash
------------------------------------------------------------------------------------
AQAAAAEAACcQAAAAEHQ+3Z9h0tiUsinNPs8B99skAqbXh0zcWlGWTgTVik6S85viEWQFV8TF8bRyDTW8rw== PhoneNumber
-----------
NULL PhoneNumberConfirmed
--------------------
0 SecurityStamp
------------------------------------
a4d9c858-cc08-4ceb-8d5d-92a6cb1c40b8 TwoFactorEnabled
----------------
0 UserName
-----------------
xxxxxxxx@163.com

Id

主键 默认是 nvarchar(450) 但事实上是存储的Guid字符串,另外值得一提的是Id的创建时机

主键的Guid是在创建用户时在构造函数中生成的

namespace Microsoft.AspNetCore.Identity
{
public class IdentityUser : IdentityUser<string>
{
public IdentityUser()
{
Id = Guid.NewGuid().ToString();
}

这是一小段源代码,用来证明上述内容

也就是说它是完全随机的无序Guid,那么它可能带来的隐患就是当用户量非常大的时候,创建用户可能变慢,不过对于绝大多数情景来讲,这不太可能(有那么多的用户),当然这可能发生,所以在后续的文章里,我会讲解如何使用bigint作为主键

AccessFailedCount

这个是用来记录用户尝试登陆却登陆失败的次数,我们可以通过这个来确定在什么时候需要锁定用户,

ConcurrencyStamp

同步标记,每当用户记录被更改时必须要更改此列的值,事实上存储的是Guid,并且在创建用户模型的时候直接在属性上初始化随机值

public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();

另外要注意,这个列的值的更改时机,它是在程序中手动编写的代码更改的,而不是由数据库更改(可能是考虑到并不是所有ef支持的数据库都支持timestamp 或者 rowversion 类型)

Email、NormalizedEmail

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 、NormalizedUserName

UserName就是UserName NormalizedUserName 还是规范化之后的UserName,也就是转换到大写

它们的行为和上述的 Email、NormalizedEmail 一致,就不赘述了

EmailConfirmed

邮件已经确认,这是个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的范畴内了,这属于我们的程序逻辑,要不要阻止未验证邮件的用户登录,需要我们自己做,不过,很简单,只需在登陆时多写几行代码而已,这里暂时先不展开讨论

LockoutEnabled、LockoutEnd

他们的数据类型是 bit和datetimeoffset(7),LockoutEnabled指示这个用户可不可以被锁定,LockoutEnd指定锁定的到期日期,null 或者一个过去的时间,代表这个用户没有被锁定

需要注意的是Identity为我们实现了锁定功能的基础设施,但是是否在用户锁定之后禁止用户登录是属于我们程序的逻辑的

PasswordHash

密码哈希,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 他们的对应关系如下

  • v1 、v2 -> Version 2
  • v3 -> Version 3

SecurityStamp

安全标记,一个随机值,在用户凭据相关的内容更改时,必须更改此项的值,事实存储的是Guid

它的更改时机有:

  • 用户创建
  • 更改用户名
  • 移除外部登陆
  • 设置/更改邮件
  • 设置/更改电话号码
  • 设置/更改双因子验证
  • 更改密码

    ConcurrencyStamp一样,SecurityStamp也是在程序中由代码控制更改的

PhoneNumber、PhoneNumberConfirmed

电话和电话已确认,比较容易理解

TwoFactorEnabled

指示当前用户是否开启了双因子验证

初次体验到此结束

ASP.NET Core Identity Hands On(1)——Identity 初次体验的更多相关文章

  1. ASP.NET Core Web API 索引 (更新Identity Server 4 视频教程)

    GraphQL 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(上) 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(下) [视频] 使用ASP.NET C ...

  2. ASP.NET Core 2.1 Web API + Identity Server 4 + Angular 6 + Angular Material 实战小项目视频

    视频简介 ASP.NET Core Web API + Angular 6的教学视频 我是后端开发人员, 前端的Angular部分讲的比较差一些, 可以直接看代码!!!! 这是一个小项目的实战视频, ...

  3. asp.net core 2.0 web api + Identity Server 4 + angular 5 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  4. asp.net core系列 55 IS4使用Identity密码保护API

    一.概述 OAuth 2.0资源(web api)所有者密码授权,允许客户端(Client项目)向令牌服务(IdentityServer项目)发送用户名和密码,并获取代表该用户的访问令牌.在官方文档中 ...

  5. asp.net core系列 55 IS4结合Identity密码保护API

    一.概述 OAuth 2.资源所有者密码授权允许客户端(Client项目)向令牌服务(IdentityServer项目)发送用户名和密码,并获取代表该用户的访问令牌.本篇将IS4结合asp.net c ...

  6. 【asp.net core 系列】15 自定义Identity

    0. 前言 在之前的文章中简单介绍了一下asp.net core中的Identity,这篇文章将继续针对Identity进行进一步的展开. 1. 给Identity添加额外的信息 在<[asp. ...

  7. (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server

    用 JWT 机制实现验证的原理如下图:  认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...

  8. 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  9. ASP.NET Core Identity 框架 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 框架 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 框架 前面我们使用了 N 多个章节, ...

  10. asp.net core使用identity+jwt保护你的webapi(一)——identity基础配置

    前言 用户模块几乎是每个系统必备的基础功能,如果每次开发一个新项目时都要做个用户模块,确实非常无聊.好在asp.net core给我们提供了Identity,使用起来也是比较方便,如果对用户这块需求不 ...

随机推荐

  1. unix命令自我总结

    三种参数类型 1⃣时间日期: cal times time 2⃣文字处理: ctl+v 输入控制字符 ${#str} str字符串长度 expr length $abc 同上 typeset -i x ...

  2. leetcode之旅(11)-Integer to Roman

    题目描述: Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range fr ...

  3. rails将类常量重构到数据库对应的表中之一

    问题是这样:原来代码.html.erb页面中有一个select元素,其每个item对应的是model中的类常量: <%= f.select :pay_type,Order::PAYMENT_TY ...

  4. 关于MySQL 5.6.24 解压缩版重启电脑后,无法启动的问题

    最近的项目需要用到mysql,想起以前安装过,就得应该没啥问题.也不知道是软件更新换代的问题,还是版权问题,网上找的msi版本的mysql都很难安装,一开始要安装.NET,我忍了,然后又要安装Visu ...

  5. DB2许可证文件

    与 DB2® 数据库产品相关联的许可证文件有两种类型: 基本许可证密钥和 完整许可证密钥.这些许可证密钥以纯文本格式存储,通常称为 许可证文件或 许可证权利证书. "基本"许可证未 ...

  6. Spring定时任务(一):SpringTask使用

    背景:在日常开发中,经常会用到任务调度这类程序.实现方法常用的有:A. 通过java.util.Timer.TimerTask实现. B.通过Spring自带的SpringTask. C. 通过Spr ...

  7. VirtualBox上安装64位系统

    http://blog.csdn.net/mal327/article/details/6597263 原来VirtualBox安装64位的系统需要满足以下条件: 1.64位的cpu2.安装的系统必须 ...

  8. Maven编译中的一些坑

    错误1: invalid LOC header 这个错误比较好解决,jar下载不完全,去到相应的Maven包目录,把东西删掉重新编译就行. 错误2: java.lang.TypeNotPresentE ...

  9. 接口和抽象类的区别(JDK1.8)

    1.一个类只能进行单继承,但可以实现多个接口. 2.有抽象方法的类一定是抽象类,但是抽象类里面不一定有抽象方法: 接口里面所有的方法的默认修饰符为public abstract,接口里的成员变量默认的 ...

  10. Python绘图之matplotlib基本语法

    Matplotlib 是一个 Python 的 2D绘图库,通过 Matplotlib,开发者可以仅需要几行代码,便可以生成绘图,直方图,功率谱,条形图,错误图,散点图等.当然他也是可以画出3D图形的 ...