1. 背景
ASP.NET Core 基於聲明的訪問控制到底是什么鬼?
聊到基於聲明的身份認證將 身份和簽發機構分離,應用程序信任簽發機構,故認可簽發的身份信息。
-- | --- | --- | --- |
---|---|---|---|
Claim | B站:438962688 Name:飯思思_ | weibo:538210234 Name:飯思思van | 姓名:不詳 籍貫:九江 |
ClaimsIdentity | 嗶哩嗶哩賬戶 | 微博賬戶 | 身份證 |
ClaimsPrincipal |
於是我們通常會有如下:
var claims = new[] {
new Claim(nameof(ClaimTypes.NameIdentifier),_authData.Data["userId"].ToString(),ClaimValueTypes.String),
new Claim(nameof(ClaimTypes.Name),_authData.Data["userName"].ToString(),ClaimValueTypes.String),
new Claim("profileId",_authData.Data["profileId"].ToString()),
new Claim("positionId",_authData.Data["positionId"].ToString()),
new Claim("organizationId",_authData.Data["organizationId"].ToString()),
new Claim("maxAge",_authData.Data["maxAge"].ToString()),
};
// 設置身份卡片內容 、身份卡片核心Name, 這個時候HttpContext.User
var identity = new ClaimsIdentity(claims, Scheme.Name,nameof(ClaimTypes.Name),nameof(ClaimTypes.Role));
Context.User = new ClaimsPrincipal(identity);
我們現在可以在Action中使用 HttpContext.User.Identity 獲取聲明的身份信息。
當我滿心歡喜在Abp vnext中封裝的ICurrentUser接口
獲取身份信息,卻無法獲取身份信息。
ICurrentUser 封裝了身份信息,用於獲取有關當前活動的用戶信息,已經被Abp框架默認注入。
你會在ApplicationSerive、 AbpController看到只讀屬性CurrentUser, 在Abp服務和控制器中是可以即時使用的。
--- | --- |
---|---|
![]() |
| |
2. Abp用戶、租戶管理
AbpICurrentUser
獲取不到常規HttpContext.User信息,是因為使用了特定的封裝,封裝的方式我不能苟同:
以下是 ICurrentUser 接口的基本屬性:
IsAuthenticated 如果當前用戶已登錄(已認證),則返回 true. 如果用戶尚未登錄,則 Id 和 UserName 將返回 null.
Id (Guid?): 當前用戶的Id,如果用戶未登錄,返回 null.
UserName (string): 當前用戶的用戶名稱. 如果用戶未登錄,返回 null.
TenantId (Guid?): 當前用戶的租戶Id. 對於多租戶 應用程序很有用. 如果當前用戶未分配給租戶,返回 null.
Email (string): 當前用戶的電子郵件地址. 如果當前用戶尚未登錄或未設置電子郵件地址,返回 null.
Roles (string[]): 當前用戶的角色. 返回當前用戶角色名稱的字符串數組.
.....
這里面有幾個問題:
-
ICurrentUser將用戶id、租戶TenantId硬編碼為
GUID
底層產生的身份id、租戶id若不為GUID,則根本不可用。
最差的情況也應該用個泛型,由應用決定特定身份片段的類型。 -
ICurrentUser 修改了
IsAuthenticated
的取值邏輯:
- ASP.NET Core官方
認證類型不為空
,就認為用戶認證通過。
// --- 來自asp.netcore源碼:https://github.com/dotnet/runtime/blob/master/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimsIdentity.cs
public virtual bool IsAuthenticated
{
get { return !string.IsNullOrEmpty(_authenticationType); }
}
.....
- Abp官方則認為
UserId不為空
,就認為用戶認證通過。
// ---截取自abp官方源碼:Volo.Abp.Users.CurrentUser
public class CurrentUser : ICurrentUser, ITransientDependency
{
private static readonly Claim[] EmptyClaimsArray = new Claim[0];
public virtual bool IsAuthenticated => Id.HasValue;
.....
}
- ICurrentUser修改了
UserName
的取值邏輯:
- Asp.NetCore檢索聲明信息中ClaimType==某個NameClaimType的Claim值, 作為身份認證卡片Identity的Name, 更靈活
- Abp 檢索聲明信息中ClaimType=="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"的值,作為身份驗證卡片的Name, 硬編碼
Abp 將UserId、TenantId 硬編碼為GUID,已經不夠通用;
另外Abp強行變更了ASP.NET Core基於聲明的身份驗證的取值邏輯,若要我們接受,需要一點學習成本。
本次我的項目就是因為UserID、TenantId為String, 在CurrentUser中轉換失敗,Name也取值失敗。
這樣我在項目中就無法使用Abp ApplicationService、Controller的CurrentUser只讀屬性。
3. 針對Abp用戶、租戶管理的應對方法
我的策略,還是向盡量使用Abp框架,盡量做到【對修改封閉,對擴展開放】,
① 於是我仿照Abp的CurrentUser實現了適合自身項目的CurrentUser
:
public class CurrentUser: ITransientDependency
{
private static readonly Claim[] EmptyClaimsArray = new Claim[0];
public virtual string Id => _principalAccessor.Principal?.Claims?.FirstOrDefault(c => c.Type == nameof(ClaimTypes.NameIdentifier))?.Value;
public virtual string UserName => _principalAccessor.Principal?.Claims?.FirstOrDefault(c => c.Type == nameof(ClaimTypes.Name))?.Value;
public virtual string Email => _principalAccessor.Principal?.Claims?.FirstOrDefault(c => c.Type == nameof(ClaimTypes.Email))?.Value;
public virtual string TenantId => _principalAccessor.Principal?.Claims?.FirstOrDefault(c => c.Type == "profileId")?.Value;
public virtual string[] Roles => FindClaims("roleId").Select(c => c.Value).ToArray();
private readonly ICurrentPrincipalAccessor _principalAccessor;
public CurrentUser(ICurrentPrincipalAccessor principalAccessor)
{
_principalAccessor = principalAccessor;
}
public virtual Claim FindClaim(string claimType)
{
return _principalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == claimType);
}
}
}
② 編寫繼承自ApplicationService、AbpController的通用服務類、控制器類:
new關鍵字顯式隱藏從基類繼承的成員
這樣我們可以繼續使用Abp框架其他能力,利用new關鍵詞我們也刻意隱藏了原有的無用CurrentUser屬性,
其他同事也不需要額外的認知成本就可以開心地像往常一樣使用新的CurrentUser
屬性。