ASP.NET Core Identity 實戰(1)——Identity 初次體驗

ASP.NET Core Identity是用於構建ASP.NET Core Web應用程序的 成員資格系統,包括 成員資格、登陸和用戶數據存儲

這是來自於 ASP.NET Core Identity 倉庫主頁的官方介紹,若是你是個萌新你可能不太理解什麼是成員資格,那我來解釋一下,成員資格由 membership 直譯而來, membership 還有會員資格、會員身份、會員全體等相關含義,咱們能夠將其簡單直接但並不是十分恰當的理解爲用戶管理系統html

ASP.NET Core Identity(下文簡稱Identity),既然能夠理解爲用戶管理系統,那麼她天然是十分強大的,包含用戶管理的方方面面,簡單的來說包括:mysql

  1. 用戶數據存儲(使用任意你喜歡的關係型數據庫,從sqllite到mysql、sqlserver等等,由Entity Framwork 支持)
  2. 登錄、註冊外加身份認證(基於cookie的身份認證,若是你使用Vs那麼還能夠生成用於註冊登陸的用戶界面及處理代碼)
  3. 角色管理
  4. 基於聲明的認證模式Claims Based Authentication(若是你不知道Claim是什麼,不要緊你先記住這個單詞)

Ok Identity這麼好,她到底長啥樣?我怎麼用呢,接下來咱們先來作一個小小的demo體驗一下,一邊作,一邊講解web

軟件準備

  • Visual Studio 2017(越新越好,若是你沒有的話就下載Vs2017社區版,安裝很快速,與舊版本兼容,徹底免費傳送門

動手作

打開Vs的建立新項目面板依次選擇 .net core -> asp.net core web 應用程序sql

clipboard.png

選擇 web 應用程序(模型視圖控制器)->更改身份認證->我的用戶帳戶
在這以後默認會使用 sqlserver compact來存儲用戶數據數據庫

clipboard.png

Ctrl+F5運行項目json

clipboard.png

注意到右上角的 register 和 login了嗎?在咱們選擇我的身份認證的時候 Identity被自動添加到項目中,而且生成了segmentfault

  • 帳戶控制器AccountController 註冊和登錄相關的代碼都在這裏)
  • 登錄註冊頁面(還有其它的 如:確認郵件、訪問受限等等)
  • 管理控制器ManageController 這是給註冊用戶用的,主要有兩個功能,改密碼和雙因子驗證)
  • Identity可不會給你生成管理員界面哦

點擊 register 進入註冊界面,界面看起來還不錯,甚至能夠直接使用,而後咱們註冊一個帳戶安全

clipboard.png

當你點擊 register 按鈕以後,會跳轉到 數據庫遷移(若是你用過EF Core,那麼這個概念你並不會感到陌生) 確認頁面
clipboard.png微信

應用遷移後,你要等一會刷新頁面,在這段時間裏,我建議你看看遷移頁面上的信息
若是看不太懂,那麼請看下圖cookie

clipboard.png

Ok, 遷移好了以後,就會回到主頁,右上角的註冊登陸會變成你的郵箱和註銷連接,點擊你的帳戶郵箱,先看看裏面有什麼

clipboard.png

這個頁面裏的內容就在ManageController中,若是你不知道雙因子認證Two-factor authentication是什麼,不要緊,在後續講到它時再說

點一下 Send verification email 連接,不用擔憂,不會真的發送郵件

clipboard.png

Identity 提供了電子郵件驗證功能,就是一般見到的那種,郵件中會有一個加密的連接,用於驗證郵件,如何生成連接Identity已經作好了,甚至寫了郵件發送的接口——IEmailSender和一個空的實現EmailSender

namespace IdentityDemo.Services
{
    public interface IEmailSender
    {
        Task SendEmailAsync(string email, string subject, string message);
    }
}

查看數據庫

剛剛咱們註冊了一個新的用戶,那麼用戶存哪裏了?默認存儲用的是sqlserver compact,接下來咱們找到它,再看看Identity是如何設計用戶數據,另外我本身粗淺的認爲學習一個新技術最好就是先看看它把數據存成什麼樣了

在Vs上方的菜單裏依次選擇 工具->鏈接到數據庫

clipboard.png

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\{當前登陸的用戶名}\下面。 再操做一次,而後點 繼續 選擇你的數據庫文件

clipboard.png

這可能會遇到數據庫文件佔用的的狀況

clipboard.png

這是由於剛剛啓動的程序沒有退出,若是你用的是自託管啓動,那麼關閉它若是用的是IISExpress,也關閉它

clipboard.png

好了,先看看數據庫裏有什麼吧

clipboard.png

_EFMigrationsHistory 是 Ef的遷移歷史表沒必要關注此表

AspNetUserClaimsAspNetRoleClaims是用戶和角色的聲明表,以前咱們提到 Identity 是基於聲明的認證模式(Claims Based Authentication)的,Claim在其中扮演者很重要的角色,甚至角色(Role)都被轉換成了Claim,Claim相關會在後面專門講解,若是你不瞭解它,不要着急

AspNetUsersAspNetRolesAspNetUserRoles存儲用戶和角色信息

AspNetUserTokensAspNetUserLogins存儲的是用戶使用的外部登錄提供商的信息和Token,外部登錄提供商指的是像微博、QQ、微信、Google、微軟這類提供 oauth 或者 openid connect 登錄的廠商。好比 segmentfault 就可使用微博登錄

接下來就要解釋下最爲重要的一張表AspNetUsers

用戶數據核心存儲—— 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

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 初次體驗
相關文章
相關標籤/搜索