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 初次體驗