"ABP入門系列目錄——學習Abp框架之實操演練" "源碼路徑:Github LearningMpaAbp" 一、AbpSession是Session嗎? 1、首先來看看它們分別對應的類型是什麼? 查看源碼發現 是定義在Controller中的類型為 的屬性。 再來看看 是何須類也,咱們定位到 中看 ...
ABP入門系列目錄——學習Abp框架之實操演練
源碼路徑:Github-LearningMpaAbp
一、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();
}
將上面兩部分代碼一中和,AbpSession中的UserId不就是這樣獲得的:
((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
好了一切一目瞭然了,AbpSession最終依賴的是ClaimsPrincipal
,並不是Session
。
所以AbpSession不是Session!!!
所以AbpSession不是Session!!!
所以AbpSession不是Session!!!
那ClaimsPrincipal
又是什麼鬼?我就喜歡你這打破砂鍋問到底的勁,且聽我娓娓道來。
二、Identity身份認證
本節主要參考自博客園Savorboard的博文,在此感謝Savorboard的精彩分享,建議大家去細細品讀一番:
ASP.NET Core 之 Identity 入門(一)
ASP.NET Core 之 Identity 入門(二)
ASP.NET Core 之 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; }
//這是我的證件類型,也很重要,同樣不許 set
public 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
上一節已經理清了思路,這一節咱們就擼起袖子擴展吧。
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、登錄前添加Cliam(身份信息)
定位到AccountController,修改SignInAsync方法,在調用AuthenticationManager.SignIn
之前添加下麵代碼:
identity.AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
5、無圖無真相
本文參考了以下博文,在此再次感謝它們的精彩分享:
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