ASP.NET Core Identity Hands On(2)——註冊、登錄、Claim

2021-03-02 dotNET跨平臺

上一篇文章(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 的基本組成是 type和value,上面票據中左側的就是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個屬性

然後我們看一下具體的登陸過程,這裡仍舊是一個表格,

登陸過程描述代碼範圍作用我們的代碼
從用戶輸入獲取用戶名、密碼、記住我
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.Claims    public 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:

不過登陸過程中,這個值是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

相關焦點

  • asp.net core 使用 TestServer 來做集成測試
    asp.net core 使用 TestServer 來做集成測試Intro之前我的項目裡的集成測試是隨機一個埠,每次都真實的啟動一個 WebServer,之前也有看到過微軟文檔上 TestServer 的介紹,當時沒仔細看過以為差不多就沒用,一直是啟動了一個真正的
  • asp.net core 5.0 中的 JsonConsole
    asp.net core 5.0 中的 JsonConsoleIntroasp.net core 5.0 中日誌新增了 JsonConsole,還是輸出日誌到 Console,但是會應用 Json 格式的一個 Formatter 把日誌格式化成 json 再輸出到控制臺
  • 簡明 ASP.NET Core 手冊
    我寫這本小書的目的,是幫助開發者和愛好者了解 ASP.NET Core 2.0,一個嶄新的,用於創建 Web應用 和 API 的框架。這本 簡明 ASP.NET Core 手冊 內容組織成了一篇教程。你將從零開始,完整地構建一個 待辦事項(to-do) 應用,同時了解以下內容:MVC (Model-View-Controller) 模式的基本內容前端代碼(HTML, CSS, JavaScript)怎樣與後端代碼交互什麼是依賴注入以及它的實用之處如何進行資料庫的讀寫操作如何添加 登錄、註冊功能,以及如何提升安全性
  • 在ASP.NET Core中使用Angular2,以及與Angular2的Token base身份認證
    注意:本文章屬於Step by step + Code Sample教程,且篇幅較長,建議下載本Sample並跟著本文進度自己重做一遍本例,下載完整代碼並分析代碼結構才有意義,下載地址:How to authorization Angular 2 app with asp.net core web api
  • ASP.NET Core MVC四種枚舉綁定方式
    所以到此我們研究結束,此方法應該是被.net core mvc團隊已經棄用,我們繼續往下看。雖然上述是.net core提供給我們最好的方案,確實很好,但是我們實際要的效果不是這樣,我們來舉一個實際場景,比如如下枚舉類。
  • ASP.Net Core Razor 頁面路由
    原文:《Routing in Razor Pages》https://www.mikesdotnetting.com/article/310/routing-in-razor-pages翻譯:Sweet Tang本文地址:http://www.cnblogs.com/tdfblog/p/razor-pages-route-in-asp-net-core.html
  • ASP.NET Core - Razor頁面之Handlers處理方法
    示例項目可在GitHub上找到,需要使用最新的.NET Core 2.0.0 CLI。<input type="submit" value="Save First" asp-page-handler="First" class="btn btn-primary btn-xs" />    </form>    <form method="POST">        <div>Description: <input asp-for=
  • [譯]ASP.NET Core Web API 中使用Oracle資料庫和Dapper看這篇就夠了
    Install-Package Oracle.ManagedDataAccess.Core -Version 2.12.0-beta2添加 Oracle 資料庫連接現在我們已準備好與資料庫相關的所有內容,如資料庫,表和SP等。要從Web API訪問資料庫,我們必須像往常一樣在「appsettings.json」文件中創建連接字符串。
  • 如何實現Asp與Asp.Net共享Session
    中,Session的存儲機制已經與Asp的存儲機制不一樣,雖然可以在同一個IIS下同時運行asp與aspx,但是它們之間不能傳遞Session。之前大批系統應用到了asp,在升級過程中,如果完全拋棄asp來重寫,一來工作量太大,二來以前的成果不能保存下來。所以微軟提出了一個Session共享的解決方案,只是此文檔光說明原理,並沒有說具體的操作步驟,由此,我撰文描述過程。
  • ASP.NET Core 3.1中HTTPS的配置
    HTTPS重定向中間件在asp.net中,默認情況下啟用https。其中HttpsRedirection中間件類提供強制執行從http到https重定向。啟動時會使用UseHttpsRedirection擴展方法來強制執行此操作。默認情況下,該擴展方法發出307臨時重定向響應。然後,使用配置的https埠指定重定向端點。
  • 優化 .net core 應用的 dockerfile
    優化 .net
  • Asp.Net Core之Identity應用(下篇)
    AspNetUserLogins  保留如 Google, Facebook, Twitter ,QQ等第三方登錄的信息。3.2 自定義模型上下文用於通過兩種方式配置模型:重寫 OnModelCreating 以修改這些類型的映射。
  • 【Python】建立gRPC服務端與.Net Core 客戶端
    由於需要接入其他平臺的OAuth 2.0,還要提供RESTful API獲取VMware vSphere的數據,這塊內容,.net core我更熟悉,所以鎖定ASP.NET Core,通過上兩篇,我們知道這裡只能通過python去獲取數據了,那麼我面臨的問題就是python與.net core的數據通信:方法一:使用Python的web
  • ASP.NET Core 和 Blazor 更新 - OSCHINA - 中文開源技術交流社區
    calls-deadline-exceededmessages-sentmessages-receivedcalls-unimplemented詳情請見發布說明:https://devblogs.microsoft.com/aspnet/asp-net-core-and-blazor-updates-in-net-core
  • ASP.NET Core Web API 最佳實踐
    當然,我們需要在該方法內部編寫代碼來註冊服務,但是我們可以通過使用 擴展方法 來讓我們的代碼更加地可讀和可維護。但是想像一下,在註冊了十幾個服務之後這個方法體的長度。這樣一點也不具有可讀性。public void ConfigureServices(IServiceCollection services) { services.ConfigureCors();}了解更多關於 .NET Core 的項目配置,請查看:https://code-maze.com/net-core-web-development-part2
  • NET開發-ASP.NET WebForm應用程式中,使用C#操作TextBox的屬性
    1.2. 屬性1.2.1. ID屬性ID屬性是每個asp.net每個伺服器控制項必須具備的屬性,用來指定控制項的唯一ID值。TextBox控制項的ID值最好以「txt+有意義的單詞」組成,便於後臺C#代碼中識別並賦值和獲取值。1.2.2.
  • .net core+Spring Cloud學習之路 一
    言歸正傳,在今早發現張隊在元旦前的博客「年末展望:Oracle 對 JDK收費和.NET Core 給我們的機遇」,說明了以後.net core會越來越好,而我本人在2017年的時候開始接觸過.net core的時候,就沒有放棄過對.net core的學習,現在覺得微服務是個不錯的方向,而自己也在學習這塊的東西,所以寫個博客記錄一下自己的學習的筆記。
  • ASP.NET Web 開發課程的教與學
    (3)http://www.asp.net/.Microsoft asp.net官方網站     二、課程學時分配表序號章節名稱講課學時(1)使用常見的Web伺服器控制項(文本,下拉列表,單選鈕,按鈕等等);(2)使用驗證控制項對輸入內容進行驗證;(3)伺服器端接收註冊信息,並導航到註冊成功頁面。 實驗四:ASP.NET內置對象的使用(2學時)要求:在原來註冊頁面的基礎上,實現客戶端和伺服器端的交互。
  • ASP.Net安裝簡明手冊
    forinstall):250MB·Availableharddiskspace(postinstall):155MB·Video:800x600,256colors·CD-ROM:required·OperatingSystem:MicrosoftWindows2000andMicrosoftInternetExplorer5.5·OtherSoftware:MDAC2.6Beta2
  • asp.net mvc 自定義 pager 封裝與優化
    asp.net