ABP入门系列(10)——扩展AbpSession
一、AbpSession是Session嗎?
1、首先來看看它們分別對應的類型是什么?
查看源碼發現Session是定義在Controller中的類型為HttpSessionStateBase的屬性。 public HttpSessionStateBase Session { get; set; }
再來看看AbpSession是何須類也,咱們定位到AbpController中看一看。 public IAbpSession AbpSession { get; set; }
好吧,原來AbpSession是IAbpSession類型啊。但這就可以斷定AbpSession不是Session嗎? 未必吧,如果IAbpsession的具體實現中還是依賴Session也不一定哦,如果是這樣,那AbpSession可以算作Session的擴展,也可以說是Session。 咱還是找找IAbpsession的具體實現一探究竟吧。 Abp中對IAbpsession有兩個實現方式,一種是NullAbpSession,NullAbpSession是空對象設計模式,用于屬性注入時,在構造函數中對其初始化。 另一種是ClaimsAbpSession,咱們來一探究竟。
2、一探究竟ClaimsAbpSession
以下代碼是ClaimsAbpSession的節選:
/// <summary> /// Implements <see cref="IAbpSession"/> to get session properties from claims of <see cref="Thread.CurrentPrincipal"/>. /// </summary> public class ClaimsAbpSession : IAbpSession, ISingletonDependency {public virtual long? UserId{get{var userIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);if (string.IsNullOrEmpty(userIdClaim?.Value)){return null;}long userId;if (!long.TryParse(userIdClaim.Value, out userId)){return null;}return userId;}}public IPrincipalAccessor PrincipalAccessor { get; set; }public ClaimsAbpSession(IMultiTenancyConfig multiTenancy){MultiTenancy = multiTenancy;PrincipalAccessor = DefaultPrincipalAccessor.Instance;} }其中IPrincipalAccessor又是什么鬼,從構造函數來看,DefaultPrincipalAccessor應該是個單例模式。
public class DefaultPrincipalAccessor : IPrincipalAccessor, ISingletonDependency {public virtual ClaimsPrincipal Principal => Thread.CurrentPrincipal as ClaimsPrincipal;public static DefaultPrincipalAccessor Instance => new DefaultPrincipalAccessor(); }其中public static DefaultPrincipalAccessor Instance => new DefaultPrincipalAccessor();是屬性表達式寫法,相當于:
public static DefaultPrincipalAccessor Instance { get { new DefaultPrincipalAccessor();} }所以并非是單例模式(長了個記性,并不是定義了Instance屬性的就是單例)
將上面兩部分代碼一中和,AbpSession中的UserId不就是這樣獲得的: ((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
好了一切一目了然了,AbpSession最終依賴的是ClaimsPrincipal,并不是Session。
所以AbpSession不是Session!!! 所以AbpSession不是Session!!! 所以AbpSession不是Session!!!
那ClaimsPrincipal又是什么鬼?我就喜歡你這打破砂鍋問到底的勁,且聽我娓娓道來。
二、Identity身份認證
1、Cliam(身份信息)
拿身份證舉例,其中包括姓名:奧巴馬、性別:男、民族:xx、出生:xx、住址:xx、公民省份號碼:xxx,這些鍵值對都是身份信息。其中姓名、性別、民族、出生、住址、公民省份號碼這些是身份信息類別(ClaimsType),微軟已經給我們預定義了一系列的身份信息類別,其中包括(Email、Gender、Phone等等)。
2、ClaimsIdentity(身份證)
有了身份信息,一組裝,不就成了身份證。 看下ClaimsIdentity的簡要代碼:
public class ClaimsIdentity: IIdentity {public virtual IEnumerable<Claim> Claims{get { //省略其他代碼 }}//名字這么重要,當然不能讓別人隨便改啊,所以我不許 set,除了我兒子跟我姓,所以是 virtual 的public virtual string Name { get; }//這是我的證件類型,也很重要,同樣不許 setpublic virtual string AuthenticationType { get; }public virtual void AddClaim(Claim claim);public virtual void RemoveClaim(Claim claim);public virtual void FindClaim(Claim claim); }可以看到ClaimsIdentity維護了一個Claim枚舉列表。 其中AuthenticationType,從字面意思理解是驗證類型。什么意思呢?比如我們拿身份證去政府部門辦理業務時,有時需要持本人身份證,但有時候需要身份證復印件即可。
3、ClaimsPrincipal (證件所有者)
我們用身份信息構造了一個身份證,這個身份證肯定是屬于具體的某個人吧。 所以ClaimsPrincipal就是用來維護一堆證件的。 因為現實生活中也是這樣,我們有身份證、銀行卡、社保卡等一系列證件。 那咱們就來看.net中是怎樣實現的:
//核心代碼部分 public class ClaimsPrincipal :IPrincipal {//把擁有的證件都給當事人public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities){}//當事人的主身份呢public virtual IIdentity Identity { get; }public virtual IEnumerable<ClaimsIdentity> Identities { get; }public virtual void AddIdentity(ClaimsIdentity identity);//為什么沒有RemoveIdentity , 留給大家思考吧? }了解了這些概念,我們再來看看Identity的簡要登陸流程:
從這張圖來看,我們登陸的時候提供一些身份信息Claim(用戶名/密碼),然后Identity中間件根據這些身份信息構造出一張身份證ClaimsIdentity,然后把身份證交給ClaimsPrincipal證件所有者保管。
三、捋一捋Abp中的登陸流程
定位到AccountController,關注下以下代碼:
[HttpPost] [DisableAuditing] public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "") {CheckModelState();var loginResult = await GetLoginResultAsync(loginModel.UsernameOrEmailAddress,loginModel.Password,loginModel.TenancyName);await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe);if (string.IsNullOrWhiteSpace(returnUrl)){returnUrl = Request.ApplicationPath;}if (!string.IsNullOrWhiteSpace(returnUrlHash)){returnUrl = returnUrl + returnUrlHash;}return Json(new AjaxResponse { TargetUrl = returnUrl }); }private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName) {var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);switch (loginResult.Result){case AbpLoginResultType.Success:return loginResult;default:throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);} }private async Task SignInAsync(User user, ClaimsIdentity identity = null, bool rememberMe = false) {if (identity == null){identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);}AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberMe }, identity); }分析發現主要包括以下幾個步驟: 1、 GetLoginResultAsync --> loginManager.LoginAsync --> userManager.CreateIdentityAsync:不要以為調用了LoginAsync就以為是登錄,其實這是偽登錄。主要根據用戶名密碼去核對用戶信息,構造User對象返回,然后再根據User對象的身份信息去構造身份證(CliamsIdentity)。 2、SignInAsync --> AuthenticationManager.SignOut -->AuthenticationManager.SignIn : AuthenticationManager(認證管理員),負責真正的登入登出。SignIn的時候將第一步構造的身份證(CliamsIdentity)交給證件所有者(ClaimsPrincipal)。
是不是明白該怎么擴展AbpSession了? 關鍵是往身份證(CliamsIdentity)中添加身份信息(Cliam)啊!!!
其實去github上Abp官網搜issue,發現土耳其大牛也是給的這種擴展思路,詳參此鏈。
四、開始擴展AbpSession(第一種方式:推薦)
上一節已經理清了思路,這一節咱們就擼起袖子擴展吧。 現在假設我們需要擴展一個Email屬性:
1、登錄前添加Cliam(身份信息)
定位到AccountController,修改SignInAsync方法,在調用AuthenticationManager.SignIn之前添加下面代碼: identity.AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
2、定義IAbpSession擴展類獲取擴展屬性
既然只要我們在登錄的時候通過在身份信息中添加要擴展的屬性,我們就可以通過ClaimsPrincipal中獲取擴展的屬性。 所以我們可以通過對IAbpSession進行擴展,通過擴展方法從CliamsPrincipal中獲取擴展屬性。
所以我們需要在領域層,也就是.Core結尾的項目中對IAbpSession進行擴展。定位到.Core結尾的項目中,添加Extensions文件夾,添加擴展類AbpSessionExtension2:
namespace LearningMpaAbp.Extensions {/// <summary>/// 通過擴展方法來對AbpSession進行擴展/// </summary>public static class AbpSessionExtension2{public static string GetUserEmail(this IAbpSession session){return GetClaimValue(ClaimTypes.Email);}private static string GetClaimValue( string claimType){var claimsPrincipal = DefaultPrincipalAccessor.Instance.Principal;var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);if (string.IsNullOrEmpty(claim?.Value))return null;return claim.Value;}} }通過擴展類,我們不需要做其他額外的更改,即可通過ApplicationService, AbpController 和 AbpApiController 這3個基類已經注入的AbpSession屬性調用GetUserEmail()來獲取擴展的Email屬性。
這種方式時最簡單的方式,推薦此種方法!!!
五、開始擴展AbpSession(第二種方式)
ApplicationService, AbpController 和 AbpApiController 這3個基類已經注入了AbpSession屬性。 所以我們需要在領域層,也就是.Core結尾的項目中對AbpSession進行擴展。 現在假設我們需要擴展一個Email屬性。
1、擴展IAbpSession
定位到.Core結尾的項目中,添加Extensions文件夾,然后添加IAbpSessionExtension接口繼承自IAbpSession:
namespace LearningMpaAbp.Extensions {public interface IAbpSessionExtension : IAbpSession{string Email { get; }} }2、實現IAbpSessionExtension
添加AbpSessionExtension類,繼承自ClaimsAbpSession并實現IAbpSessionExtension接口。
namespace LearningMpaAbp.Extensions {public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension{public AbpSessionExtension(IMultiTenancyConfig multiTenancy) : base(multiTenancy){}public string Email => GetClaimValue(ClaimTypes.Email);private string GetClaimValue(string claimType){var claimsPrincipal = PrincipalAccessor.Principal;var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);if (string.IsNullOrEmpty(claim?.Value))return null;return claim.Value;}} }3、替換掉注入的AbpSession屬性
先來替換掉AbpController中注入的AbpSession 定位到.Web\Controllers\xxxxControllerBase.cs,使用屬性注入IAbpSessionExtension。添加以下代碼:
//隱藏父類的AbpSession public new IAbpSessionExtension AbpSession { get; set; }再來替換掉ApplicationService中注入的AbpSession 定位到.Application\xxxxAppServiceBase.cs。使用屬性注入IAbpSessionExtension,同樣添加以下代碼:
//隱藏父類的AbpSession public new IAbpSessionExtension AbpSession { get; set; }至于AbpApiController要不要替換AbpSession,就視情況而定了,如果你使用的是Abp提供的動態WebApi技術,就不需要替換了,因為畢竟最終調用的是應用服務層的Api。如果WebApi是自己代碼實現的,那就仿照上面自行替換吧,就不羅嗦了。
很顯然,這種方式教第一種方式要麻煩許多。。。
4、無圖無真相
總結:
本文首先對AbpSession一探真面目,了解到AbpSession不是Session; 然后對Identity身份認證流程就行簡要剖析,發現AbpSession是依賴于ClaimsPrincipal,從而確定擴展AbpSession的思路:關鍵是往身份證(CliamsIdentity)中添加身份信息(Cliam)啊!!!; 最終提供了兩種擴展思路: 其中推薦通過對IAbpSession進行擴展,通過擴展方法從CliamsPrincipal中獲取擴展屬性。 本文參考了以下博文,在此再次感謝它們的精彩分享:
ASP.NET Core 之 Identity 入門(一)--Savorboard ASP.NET Core 之 Identity 入門(二)--Savorboard ASP.NET Core 之 Identity 入門(三)--Savorboard Asp.net Boilerplate之AbpSession擴展--kid1412 基于DDD的.NET開發框架 - ABP Session實現--Joye.Net
閱罷此文,如果您覺得本文不錯并有所收獲,請【打賞】或【推薦】,也可【評論】留下您的問題或建議與我交流。 你的支持是我不斷創作和分享的不竭動力!
總結
以上是生活随笔為你收集整理的ABP入门系列(10)——扩展AbpSession的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网友曝特斯拉Model 3突然暴降6万!
- 下一篇: VS的TFS版本控制的使用总结