ASP.NET Core 之 Identity 入门(三)
前言
在上一篇文章中,我們學習了 CookieAuthentication 中間件,本篇的話主要看一下 Identity 本身。
最早2005年 ASP.NET 2.0 的時候開始, Web 應用程序在處理身份驗證和授權有了很多的變化,多了比如手機端,平板等,所以那個時候為了適應這種變化就引入了ASP.NET Membership,但是隨著時間的發展一些社交網站或者程序聚集了大量的用戶,比如Facebook,Twitter,QQ等,這個時候用戶希望能夠使用他們在這些社交站點身份來登陸當前網站,這樣可以免除注冊這些瑣碎而又必要的操作,用戶也不必記住大量的賬戶密碼。
又隨著互聯網的發展,越來越多的開發者不只是關注具體業務代碼的編寫,轉變為開始關注應用程序代碼的單元測試,這已經是開發者關注的核心。所以在2008年,ASP.NET 團隊引入了 MVC 框架,這樣來幫助開發者很方便的構建單元測試,同時開發者希望他們的 Membership 系統也能夠做到這一點。
基于以上,ASP.NET Identity 應運而生。
Identity 要解決的問題
很多開發人員說他們不愿意使用Identity,自己實現要方便的多,OK,那么需求來了?以下就是我針對此次任務給你提出來的需求。
身份系統
可以同時被所有的ASP.NET 框架使用(Web MVC,Web Forms,Web Api,SignalR)
可以應用于構建 Web, 手機,存儲,或者混合應用。
能夠對用戶資料(User Profile)很方便的擴展
可以針對用戶資料進行擴展。
持久化
默認把用戶信息存儲在數據庫中,可以支持使用EF進行持久化。(可以看到,EF 其實只是Identity的一個功能點而已)
可以控制數據庫架構,更改表名或者主鍵的數據類型(int,string)
可以使用不同的存儲機制(如 NoSQL,DB2等)
單元測試
使WEB 應用程序可以進行單元測試,可以針對ASP.NET Identity編寫單元測試
角色機制
提供角色機制,可以使用不同的角色來進行不同權限的限制,可以輕松的創建角色,向用戶添加角色等。
要支持基于Claims
需要支持基于 Claims 的身份驗證機制,其中用戶身份是一組Claims,一組Claims可以比角色擁有更強的表現力,而角色僅僅是一個bool值來表示是不是會員而已。
第三方社交登陸
可以很方便的使用第三方登入,比如 Microsoft 賬戶,Facebook, Twitter,Google等,并且存儲用戶特定的數據。
封裝為中間件
基于中間件實現,不要對具體項目產生依賴
基于 Authorzation 中間件實現,而不是使用 FormsAuthentication 來存儲cookie。
NuGet包提供
發布為 Nuget 包,這樣可以容易的進行迭代和bug修復,可以靈活的提供給使用者。
以上,就是我提出來的需求,如果讓你來封裝這樣一個用戶身份認證組件,你會不是想到以上的這些功能點,那針對于這些功能點你又會怎么樣來設計呢?
下面來看一下 Identity 怎么樣設計的吧。
Getting Started
抽絲剝繭,我們先從入口看一下其使用方式。 首先我們打開 Startup.cs 文件,然后添加如下代碼:
public class Startup{ ? ?public void ConfigureServices(IServiceCollection services) ? ?{services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));services.AddIdentity<ApplicationUser, IdentityRole>(options => {options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie";options.Cookies.ApplicationCookie.CookieName = "Interop";}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();} ? ?public void Configure(IApplicationBuilder app) ? ?{ ? ? ? ?// 使用了 CookieAuthentication 中間件做身份認證app.UseIdentity();} }在 ConfigureServices 中,先是注冊了數據庫上下文,然后又?services.AddIdentity()?我們看一下里面都注冊了哪些服務呢?
public static IdentityBuilder AddIdentity<TUser, TRole>(this IServiceCollection services,Action<IdentityOptions> setupAction)where TUser : classwhere TRole : class{ ? ?// 這個就是被 Identity 使用的services.AddAuthentication(options =>{ ? ? ? ?// This is the Default value for ExternalCookieAuthenticationSchemeoptions.SignInScheme = new IdentityCookieOptions().ExternalCookieAuthenticationScheme;}); ? ?// 注冊 IHttpContextAccessor ,會用到services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); ? ?// Identity servicesservices.TryAddSingleton<IdentityMarkerService>();services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>(); ? ?// 錯誤描述信息services.TryAddScoped<IdentityErrorDescriber>();services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>(); ? ?//身份當事人工廠services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>(); ? ?//三大對象services.TryAddScoped<UserManager<TUser>, UserManager<TUser>>();services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();services.TryAddScoped<RoleManager<TRole>, RoleManager<TRole>>();if (setupAction != null){services.Configure(setupAction);}return new IdentityBuilder(typeof(TUser), typeof(TRole), services); } ? ?看了以上代碼后,基本上知道了 Identity 他的設計的一個架構了,通過此結構我們也能夠學習到我們自己封裝一個中間件的時候,該怎么樣來組織我們的代碼結構,怎么樣的利用 ASP.NET Core 給我們提供的依賴注入來更好的解耦,下面我們來看一下通過以上代碼我們能夠學到什么東西:
1、 在?public static IdentityBuilder AddIdentity<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction)這個擴展方法中,提供了一個參數?Action<IdentityOptions>,這個是干什么用的呢? 這個是我們在設計一個中間件的時候,有需要外部提供的參數,就會設計一個 Options 的類用來接受外部的參數,然后封裝為一個Action委托的方式提供。在使用到的地方就可以以 IOption xxx 的形式注入進來使用了。
2、services.TryAddScoped<Interface,Implement>(),這個注冊方式就是說,如果檢測到DI容器里面已經有了當前要注冊的Interface或者Service,就不會再次注冊,沒有才會注冊進去。 那么為什么此處要這樣用呢? 這是因為如果用戶已經實現了此接口并且已經注冊的容器當中的話,就使用用戶注冊的,而不是中間件自身的。用戶就能很好的對中間件提供的功能進行自定義了,這就是OO中的多態性,這就是里氏替換原則。
3、如果你能理解第2條的話,那么你應該知道為什么會在服務中注冊 錯誤描述信息 那個IdentityErrorDescriber?了吧,也能夠解決你想提示賬號密碼錯誤,無奈Identity輸出是英文問題的提示。
4、三大對象,這個是 Identity 的核心了,所以學習 Identity 的話,在看完博客 ASP.NET Core 之 Identity 入門(一,二)之后,學這三個對象就夠了。
SignInManager: 主要處理注冊登錄相關業務邏輯。
UserManager: 處理用戶相關添加刪除,修改密碼,添加刪除角色等。
RoleManager:角色相關添加刪除更新等。
有些同學可能很好奇,都沒有依賴具體的數據庫或者是EF,是怎么樣做到的增刪改查的呢?
這個時候,就需要幾個 Store 接口派上用場了。以下是Identity中定義的Store接口:
IQueryableRoleStore
IQueryableUserStore
IRoleClaimStore
IRoleStore
IUserAuthenticationTokenStore
IUserClaimStore
IUserEmailStore
IUserLockoutStore
IUserLoginStore
IUserPasswordStore
IUserPhoneNumberStore
IUserRoleStore
IUserSecurityStampStore
IUserStore
IUserTwoFactorStore
有了這些接口之后,是不是豁然開朗了,原來 Identity 是通過這種方式實現的持久化機制,依賴抽象接口而不是依賴具體的細節實現,這就是面向對象中的依賴倒置原則呀。
Identity 和 EntityFramework
Identity 和 EntityFramework的關系,相信上個章節看懂了之后,就很容易明白了,對的,EF 只是針對于上述 Store 接口的實現,不信你看截圖的源碼:
Identity 打頭的那些類文件都是定義的需要持久化的Entity對象,Store結尾的那些就是接口的實現啦。
第三方的 Identity 實現
除了 EF 是官方默認提供的持久化庫之外,還有一些第三方的庫,當然你也可以自己使用 ADO.NET 或者 Drapper 實現。
MangoDb 針對于 Identity 提供的實現:?https://github.com/tugberkugurlu/AspNetCore.Identity.MongoDB
LinqToDB 針對于 Identity 提供的實現:https://github.com/linq2db/LinqToDB.Identity
總結
這篇博文寫了蠻久的時間的,一方面是因為在構思怎么樣的思路來讓大家更好的理解,而不僅僅是使用。因為有太多的文章介紹Identity 的使用方式以及代碼了,但是最后大家還是不會用。后來想到如果讓別人想要理解你的庫也好代碼也好,讓其知道誕生的背景是很重要的,因為這才是設計的初衷。另一方面是因為Connect() 2016 大會上,.NET Core 發布了 1.1 版本,除了把項目升級到1.1之外,也在學習1.1新的一些東西,以便更好給大家分享。
相關文章:?
ASP.NET Core 之 Identity 入門(一)
ASP.NET Core 之 Identity 入門(二)
ASP.NET Identity登入技術應用
Web API 基于ASP.NET Identity的Basic Authentication
ASP.NET MVC 隨想錄——開始使用ASP.NET Identity,初級篇
原文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-identity3.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的ASP.NET Core 之 Identity 入门(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.NET Core Kestrel
- 下一篇: 利用 async amp; await