0. 前言
在之前的文章中簡單介紹了一下asp.net core中的Identity,這篇文章將繼續針對Identity進行進一步的展開。
1. 給Identity新增額外的資訊
在《【asp.net core 系列】13 Identity 身份驗證入門》一文中,我們大概瞭解瞭如何使用Identity,以及如何儲存一些資訊以便後續的驗證。這裡我們將深入討論一下如何給Identity新增更多的資訊。
我們知道在給Identity新增資料的時候,需要新增一個Claim物件。我們先回顧一下Claim的資訊,Claim的屬性大多隻提供了公開的get訪問器,所以這個類的重點在於構造方法:
public class Claim
{
// 基礎的
public Claim(string type, string value);
public Claim(string type, string value, string valueType);
public Claim(string type, string value, string valueType, string issuer);
public Claim(string type, string value, string valueType, string issuer, string originalIssuer);
//
public Claim(BinaryReader reader);
public Claim(BinaryReader reader, ClaimsIdentity subject);
}
暫且看一下幾個使用字元型別的建構函式引數:
- type Claim的型別,支援自定義,但asp.net core 提供了一個基礎的型別定義:
public static class ClaimTypes
{
// 隱藏其他屬性
public const string Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
}
這個類裡定義了大多數情況下會用到的Claims的型別。
-
value 存放Claim的值,通常情況下不對這個值進行約束
-
valueType 表示 value的型別,取值範圍參考類:
public static class ClaimValueTypes { public const string Base64Binary = "http://www.w3.org/2001/XMLSchema#base64Binary"; public const string UpnName = "http://schemas.xmlsoap.org/claims/UPN"; public const string UpnName = "http://schemas.xmlsoap.org/claims/UPN"; public const string UInteger32 = "http://www.w3.org/2001/XMLSchema#uinteger32"; public const string Time = "http://www.w3.org/2001/XMLSchema#time"; public const string String = "http://www.w3.org/2001/XMLSchema#string"; public const string Sid = "http://www.w3.org/2001/XMLSchema#sid"; public const string RsaKeyValue = "http://www.w3.org/2000/09/xmldsig#RSAKeyValue"; public const string Rsa = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa"; public const string Rfc822Name = "urn:oasis:names:tc:xacml:1.0:data-type:rfc822Name"; public const string KeyInfo = "http://www.w3.org/2000/09/xmldsig#KeyInfo"; public const string Integer64 = "http://www.w3.org/2001/XMLSchema#integer64"; public const string X500Name = "urn:oasis:names:tc:xacml:1.0:data-type:x500Name"; public const string Integer32 = "http://www.w3.org/2001/XMLSchema#integer32"; public const string HexBinary = "http://www.w3.org/2001/XMLSchema#hexBinary"; public const string Fqbn = "http://www.w3.org/2001/XMLSchema#fqbn"; public const string Email = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; public const string DsaKeyValue = "http://www.w3.org/2000/09/xmldsig#DSAKeyValue"; public const string Double = "http://www.w3.org/2001/XMLSchema#double"; public const string DnsName = "http://schemas.xmlsoap.org/claims/dns"; public const string DaytimeDuration = "http://www.w3.org/TR/2002/WD-xquery-operators-20020816#dayTimeDuration"; public const string DateTime = "http://www.w3.org/2001/XMLSchema#dateTime"; public const string Date = "http://www.w3.org/2001/XMLSchema#date"; public const string Boolean = "http://www.w3.org/2001/XMLSchema#boolean"; public const string Base64Octet = "http://www.w3.org/2001/XMLSchema#base64Octet"; public const string Integer = "http://www.w3.org/2001/XMLSchema#integer"; public const string YearMonthDuration = "http://www.w3.org/TR/2002/WD-xquery-operators-20020816#yearMonthDuration"; }
-
issuer 用來存放 Claim的釋出者,預設值是:
ClaimsIdentity.DefaultIssuer
該值允許在建構函式是傳NULL,一旦傳NULL,則自動認為是ClaimsIdentity.DefaultIssuer
-
originalIssuer Claim的原發布人,如果不給值,則預設與issuer一致。
這是從建構函式以及相關文件中獲取到的。
關於ClaimTypes裡我只貼了兩個,原因是這兩個值在Claim中是兩個必不可少的值。根據屬性名就能看出來,一個是設定使用者的名稱,一個是設定使用者的角色。
那麼,繼續探索Claim裡的屬性和方法:
public class Claim
{
public string Type { get; }
public ClaimsIdentity Subject { get; }
public IDictionary<string, string> Properties { get; }
public string OriginalIssuer { get; }
public string Issuer { get; }
public string ValueType { get; }
public string Value { get; }
public virtual Claim Clone();
public virtual Claim Clone(ClaimsIdentity identity);
public virtual void WriteTo(BinaryWriter writer);
}
幾個基本屬性都是從建構函式中獲取的,這裡就不做過多的介紹了。不過值得注意的一點是,Properties這個屬性的值獲取是需要使用
public Claim(BinaryReader reader, ClaimsIdentity? subject)
這個構造方法才可以有效的對其進行賦值,所以這個屬性並沒有太多值得關注的地方。
介紹完了Claim類之後,我們繼續看一下Identity相關的常用類:
public class ClaimsIdentity : IIdentity;
通過這個類的宣告,我們可以看出它實現了介面:
public interface IIdentity
{
string? AuthenticationType { get; }
bool IsAuthenticated { get; }
string? Name { get; }
}
其中
- AuthenticationType 表示驗證型別
- IsAuthenticated 表示是否驗證通過
- Name 存放的使用者名稱
這是Identity裡最關鍵的三個屬性,貫穿著整個Identity體系。我們繼續看一下ClaimsIdentity的幾個關鍵點:
public class ClaimsIdentity : IIdentity
{
public ClaimsIdentity(string authenticationType);
public ClaimsIdentity(IIdentity identity);
public ClaimsIdentity(IEnumerable<Claim> claims);
public ClaimsIdentity(IEnumerable<Claim> claims, string authenticationType);
public ClaimsIdentity(IIdentity identity, IEnumerable<Claim> claims);
public virtual void AddClaim(Claim claim);
public virtual void AddClaims(IEnumerable<Claim> claims);
}
對於ClaimsIdentity而言,其核心內容是Claim例項。我們通常需要構造Claim物件,在Claim物件中新增我們想新增的值,然後裝入ClaimIdentity中。這裡有一個值需要額外注意一下:AuthenticationType 表示驗證型別,值並沒有額外要求,不過對於使用Cookie作為資訊儲存的話,需要設定值為:
CookieAuthenticationDefaults.AuthenticationScheme
這時候,我們已經獲得了一個Identity物件,在asp.net core 中 Identity體系還有最後一個關鍵類:
public class ClaimsPrincipal : IPrincipal
{
public ClaimsPrincipal();
public ClaimsPrincipal(IIdentity identity);
public ClaimsPrincipal(IPrincipal principal);
public virtual void AddIdentities(IEnumerable<ClaimsIdentity> identities);
public virtual void AddIdentity(ClaimsIdentity identity);
}
這個類提供了幾個方法用來儲存Identity,這個類在IPrincipal基礎上以Identity為基礎資料。這一點可以通過建構函式和它提供的一些方法可以確認。
2. 讀取Identity的資訊
在第一小節中,我簡單介紹了一下如何利用Claim和ClaimsIdentity以及ClaimsPrincipal這三個類來儲存使用者資訊以及我們想要的資料。這裡我們看一下如何通過Principal讀取資訊,以及簡單剖析一下背後的邏輯。
我們使用HttpContext的擴充套件方法:
public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal);
將我們設定的principal資料儲存,所儲存的地方取決於我們在Startup.cs中的設定。在該系列中,我們啟用了Cookie,所以這個資訊會以Cookie的形式儲存。
在控制器內部時,Controller類為我們提供了一個屬性:
public ClaimsPrincipal User { get; }
通過這個屬性可以反向獲取到我們儲存的Principal例項。
接下來,讓我們反向解析出Principal裡面的資料:
public interface IPrincipal
{
IIdentity? Identity { get; }
bool IsInRole(string role);
}
IPrincipal提供了兩個基礎資料和方法,一個是獲取一個Identity物件,一個是判斷是否是某個角色。
2.1 Identity
在ClaimPrincipal中,Identity屬性的預設取值邏輯是:
if (identities == null)
{
throw new ArgumentNullException(nameof(identities));
}
foreach (ClaimsIdentity identity in identities)
{
if (identity != null)
{
return identity;
}
}
return null;
也就是獲取Principal中第一個不為Null的Identity物件,這個取值邏輯可以通過下面的屬性進行修改:
public static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity?> PrimaryIdentitySelector { get; set; }
2.2 IsInRole
在Principal中,通常會存放一至多個Identity物件,每個 Identity物件有一至多個Claim物件。當有Claim物件的Type 值與Identity物件的:
public string RoleClaimType { get; }
值一致時,就會被認為該Claim裡面存放著角色資訊,這時候會通過傳入的role值與Claim的Value進行比較。
比較的方法是Identity的例項方法HasClaim:
public virtual bool HasClaim(string type, string value);
如果初始化Identity時,沒有手動設定roleType引數,那麼這個引數取值就是:
public const string DefaultRoleClaimType = ClaimTypes.Role;
通常情況下,不會單獨設定roleType。
2.3 IsAuthenticated 判斷是否登入
這個屬性並不是ClaimPrincipal的,而是ClaimIdentity的。通常在asp.net core 中會使用這個屬性判斷訪問者是否完成了身份校驗。這個屬性的判斷邏輯也很簡單:
public virtual bool IsAuthenticated
{
get { return !string.IsNullOrEmpty(AuthenticationType); }
}
也就是說,在Identity中指定了AuthenticationType就會認為完成了身份校驗。
通常的使用方式:
User.Identity.IsAuthenticated
通過以上呼叫鏈進行資料呼叫。
2.4 Name
與IsAuthenticatedy一樣,這個屬性也是ClaimIdentity的。與IsInRole的判斷依據類似,這個屬性會獲取Identity中存放的Claim集合中第一個RoleType為ClaimType.Name的Claim,然後取值。
所以,在實現登入的時候,如果想要能夠通過:
User.Identity.Name
獲取一個使用者名稱資訊或者其他名稱資訊的話,則需要設定一個Type等於:
public const string DefaultNameClaimType = ClaimTypes.Name;
的Claim例項物件。
2.5 獲取Claim
在Principal體系中,最重要也是最基礎的資料就是Claim物件。對於ClaimPrincipal物件來說,裡面必然會存放多個Claim物件。那麼,我們就需要有操作Claim物件的方法:
public virtual IEnumerable<Claim> Claims { get; }
通過這個方法可以獲得ClaimPrincipal裡所有的Claim物件,這是一個迭代器物件。
public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match);
通過一個選擇器篩選出符合條件的Claim集合。
public virtual IEnumerable<Claim> FindAll(string type);
查詢所有符合型別的Claim物件。
public virtual Claim FindFirst(string type);
查詢第一個Type值與指定值相同的Claim物件。
public virtual bool HasClaim(Predicate<Claim> match);
查詢是否存在符合條件的Claim物件。
public virtual bool HasClaim(string type, string value);
查詢是否有Type和Value屬性均等於指定值的Claim物件。
這些方法都是ClaimPrincipal裡的,相對應的ClaimIdentity裡也提供了類似的方法這裡就不做介紹了。
3. 總結
這一章介紹瞭如何利用Claim進行使用者資訊儲存,以及常規的一些使用邏輯。下一章,我們將繼續探索如何利用我們自己設定的Identity以達到我們的目的。
更多內容煩請關注我的部落格《高先生小屋》