【asp.net core 系列】15 自定義Identity

0. 前言

在之前的文章中簡(jiǎn)單介紹了一下asp.net core中的Identity狭魂,這篇文章將繼續(xù)針對(duì)Identity進(jìn)行進(jìn)一步的展開。

1. 給Identity添加額外的信息

在《【asp.net core 系列】13 Identity 身份驗(yàn)證入門》一文中蛛枚,我們大概了解了如何使用Identity,以及如何保存一些信息以便后續(xù)的驗(yàn)證脸哀。這里我們將深入討論一下如何給Identity添加更多的信息蹦浦。

我們知道在給Identity添加數(shù)據(jù)的時(shí)候,需要添加一個(gè)Claim對(duì)象撞蜂。我們先回顧一下Claim的信息盲镶,Claim的屬性大多只提供了公開的get訪問器,所以這個(gè)類的重點(diǎn)在于構(gòu)造方法:

public class Claim
{
    // 基礎(chǔ)的
    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);
}

暫且看一下幾個(gè)使用字符類型的構(gòu)造函數(shù)參數(shù):

  1. type Claim的類型蝌诡,支持自定義溉贿,但asp.net core 提供了一個(gè)基礎(chǔ)的類型定義:
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";
}

? 這個(gè)類里定義了大多數(shù)情況下會(huì)用到的Claims的類型。

  1. value 存放Claim的值浦旱,通常情況下不對(duì)這個(gè)值進(jìn)行約束

  2. 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";
    }
    
  3. issuer 用來存放 Claim的發(fā)布者,默認(rèn)值是:ClaimsIdentity.DefaultIssuer 該值允許在構(gòu)造函數(shù)是傳NULL,一旦傳NULL代兵,則自動(dòng)認(rèn)為是ClaimsIdentity.DefaultIssuer

  4. originalIssuer Claim的原發(fā)布人尼酿,如果不給值,則默認(rèn)與issuer一致植影。

這是從構(gòu)造函數(shù)以及相關(guān)文檔中獲取到的裳擎。

關(guān)于ClaimTypes里我只貼了兩個(gè),原因是這兩個(gè)值在Claim中是兩個(gè)必不可少的值思币。根據(jù)屬性名就能看出來鹿响,一個(gè)是設(shè)置用戶的名稱,一個(gè)是設(shè)置用戶的角色谷饿。

那么惶我,繼續(xù)探索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);
}

幾個(gè)基本屬性都是從構(gòu)造函數(shù)中獲取的,這里就不做過多的介紹了博投。不過值得注意的一點(diǎn)是绸贡,Properties這個(gè)屬性的值獲取是需要使用

public Claim(BinaryReader reader, ClaimsIdentity? subject)

這個(gè)構(gòu)造方法才可以有效的對(duì)其進(jìn)行賦值,所以這個(gè)屬性并沒有太多值得關(guān)注的地方毅哗。

介紹完了Claim類之后听怕,我們繼續(xù)看一下Identity相關(guān)的常用類:

public class ClaimsIdentity : IIdentity;

通過這個(gè)類的聲明,我們可以看出它實(shí)現(xiàn)了接口:

public interface IIdentity
{
    string? AuthenticationType { get; }
    bool IsAuthenticated { get; }
    string? Name { get; }
}

其中

  • AuthenticationType 表示驗(yàn)證類型
  • IsAuthenticated 表示是否驗(yàn)證通過
  • Name 存放的用戶名

這是Identity里最關(guān)鍵的三個(gè)屬性虑绵,貫穿著整個(gè)Identity體系尿瞭。我們繼續(xù)看一下ClaimsIdentity的幾個(gè)關(guān)鍵點(diǎn):

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);
}

對(duì)于ClaimsIdentity而言,其核心內(nèi)容是Claim實(shí)例翅睛。我們通常需要構(gòu)造Claim對(duì)象声搁,在Claim對(duì)象中添加我們想添加的值,然后裝入ClaimIdentity中捕发。這里有一個(gè)值需要額外注意一下:AuthenticationType 表示驗(yàn)證類型疏旨,值并沒有額外要求,不過對(duì)于使用Cookie作為信息保存的話爬骤,需要設(shè)置值為:

CookieAuthenticationDefaults.AuthenticationScheme

這時(shí)候充石,我們已經(jīng)獲得了一個(gè)Identity對(duì)象,在asp.net core 中 Identity體系還有最后一個(gè)關(guān)鍵類:

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);
}

這個(gè)類提供了幾個(gè)方法用來存儲(chǔ)Identity霞玄,這個(gè)類在IPrincipal基礎(chǔ)上以Identity為基礎(chǔ)數(shù)據(jù)骤铃。這一點(diǎn)可以通過構(gòu)造函數(shù)和它提供的一些方法可以確認(rèn)。

2. 讀取Identity的信息

在第一小節(jié)中坷剧,我簡(jiǎn)單介紹了一下如何利用Claim和ClaimsIdentity以及ClaimsPrincipal這三個(gè)類來存儲(chǔ)用戶信息以及我們想要的數(shù)據(jù)惰爬。這里我們看一下如何通過Principal讀取信息,以及簡(jiǎn)單剖析一下背后的邏輯惫企。

我們使用HttpContext的擴(kuò)展方法:

public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal);

將我們?cè)O(shè)置的principal數(shù)據(jù)保存撕瞧,所保存的地方取決于我們?cè)赟tartup.cs中的設(shè)置陵叽。在該系列中,我們啟用了Cookie丛版,所以這個(gè)信息會(huì)以Cookie的形式保存巩掺。

在控制器內(nèi)部時(shí),Controller類為我們提供了一個(gè)屬性:

public ClaimsPrincipal User { get; }

通過這個(gè)屬性可以反向獲取到我們保存的Principal實(shí)例页畦。

接下來胖替,讓我們反向解析出Principal里面的數(shù)據(jù):

public interface IPrincipal
{
    IIdentity? Identity { get; }
    bool IsInRole(string role);
}

IPrincipal提供了兩個(gè)基礎(chǔ)數(shù)據(jù)和方法,一個(gè)是獲取一個(gè)Identity對(duì)象豫缨,一個(gè)是判斷是否是某個(gè)角色独令。

2.1 Identity

在ClaimPrincipal中,Identity屬性的默認(rèn)取值邏輯是:

if (identities == null)
{
    throw new ArgumentNullException(nameof(identities));
}

foreach (ClaimsIdentity identity in identities)
{
    if (identity != null)
    {
        return identity;
    }
}

return null;

也就是獲取Principal中第一個(gè)不為Null的Identity對(duì)象好芭,這個(gè)取值邏輯可以通過下面的屬性進(jìn)行修改:

public static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity?> PrimaryIdentitySelector { get; set; }

2.2 IsInRole

在Principal中燃箭,通常會(huì)存放一至多個(gè)Identity對(duì)象,每個(gè) Identity對(duì)象有一至多個(gè)Claim對(duì)象舍败。當(dāng)有Claim對(duì)象的Type 值與Identity對(duì)象的:

public string RoleClaimType { get; }

值一致時(shí)招狸,就會(huì)被認(rèn)為該Claim里面存放著角色信息,這時(shí)候會(huì)通過傳入的role值與Claim的Value進(jìn)行比較瓤湘。

比較的方法是Identity的實(shí)例方法HasClaim:

public virtual bool HasClaim(string type, string value);

如果初始化Identity時(shí)瓢颅,沒有手動(dòng)設(shè)置roleType參數(shù)恩尾,那么這個(gè)參數(shù)取值就是:

public const string DefaultRoleClaimType = ClaimTypes.Role;

通常情況下弛说,不會(huì)單獨(dú)設(shè)置roleType。

2.3 IsAuthenticated 判斷是否登錄

這個(gè)屬性并不是ClaimPrincipal的翰意,而是ClaimIdentity的木人。通常在asp.net core 中會(huì)使用這個(gè)屬性判斷訪問者是否完成了身份校驗(yàn)。這個(gè)屬性的判斷邏輯也很簡(jiǎn)單:

public virtual bool IsAuthenticated
{
    get { return !string.IsNullOrEmpty(AuthenticationType); }
}

也就是說冀偶,在Identity中指定了AuthenticationType就會(huì)認(rèn)為完成了身份校驗(yàn)醒第。

通常的使用方式:

User.Identity.IsAuthenticated

通過以上調(diào)用鏈進(jìn)行數(shù)據(jù)調(diào)用。

2.4 Name

與IsAuthenticatedy一樣进鸠,這個(gè)屬性也是ClaimIdentity的稠曼。與IsInRole的判斷依據(jù)類似,這個(gè)屬性會(huì)獲取Identity中存放的Claim集合中第一個(gè)RoleType為ClaimType.Name的Claim客年,然后取值霞幅。

所以,在實(shí)現(xiàn)登錄的時(shí)候量瓜,如果想要能夠通過:

User.Identity.Name

獲取一個(gè)用戶名信息或者其他名稱信息的話司恳,則需要設(shè)置一個(gè)Type等于:

public const string DefaultNameClaimType = ClaimTypes.Name;

的Claim實(shí)例對(duì)象。

2.5 獲取Claim

在Principal體系中绍傲,最重要也是最基礎(chǔ)的數(shù)據(jù)就是Claim對(duì)象扔傅。對(duì)于ClaimPrincipal對(duì)象來說耍共,里面必然會(huì)存放多個(gè)Claim對(duì)象。那么猎塞,我們就需要有操作Claim對(duì)象的方法:

public virtual IEnumerable<Claim> Claims { get; }

通過這個(gè)方法可以獲得ClaimPrincipal里所有的Claim對(duì)象试读,這是一個(gè)迭代器對(duì)象。

public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match);

通過一個(gè)選擇器篩選出符合條件的Claim集合荠耽。

public virtual IEnumerable<Claim> FindAll(string type);

查詢所有符合類型的Claim對(duì)象鹏往。

public virtual Claim FindFirst(string type);

查找第一個(gè)Type值與指定值相同的Claim對(duì)象。

public virtual bool HasClaim(Predicate<Claim> match);

查詢是否存在符合條件的Claim對(duì)象骇塘。

public virtual bool HasClaim(string type, string value);

查詢是否有Type和Value屬性均等于指定值的Claim對(duì)象伊履。

這些方法都是ClaimPrincipal里的,相對(duì)應(yīng)的ClaimIdentity里也提供了類似的方法這里就不做介紹了款违。

3. 總結(jié)

這一章介紹了如何利用Claim進(jìn)行用戶信息保存唐瀑,以及常規(guī)的一些使用邏輯。下一章插爹,我們將繼續(xù)探索如何利用我們自己設(shè)置的Identity以達(dá)到我們的目的哄辣。

更多內(nèi)容煩請(qǐng)關(guān)注我的博客《高先生小屋》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赠尾,隨后出現(xiàn)的幾起案子力穗,更是在濱河造成了極大的恐慌,老刑警劉巖气嫁,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件当窗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寸宵,警方通過查閱死者的電腦和手機(jī)崖面,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梯影,“玉大人闪朱,你說我怎么就攤上這事正什⊥┎#” “怎么了拔妥?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)感猛。 經(jīng)常有香客問我七扰,道長(zhǎng),這世上最難降的妖魔是什么唱遭? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任戳寸,我火速辦了婚禮,結(jié)果婚禮上拷泽,老公的妹妹穿的比我還像新娘疫鹊。我一直安慰自己袖瞻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布拆吆。 她就那樣靜靜地躺著聋迎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枣耀。 梳的紋絲不亂的頭發(fā)上霉晕,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音捞奕,去河邊找鬼牺堰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颅围,可吹牛的內(nèi)容都是我干的伟葫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼院促,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼筏养!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起常拓,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤渐溶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后弄抬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茎辐,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年眉睹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荔茬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竹海,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丐黄,到底是詐尸還是另有隱情斋配,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布灌闺,位于F島的核電站艰争,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桂对。R本人自食惡果不足惜甩卓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蕉斜。 院中可真熱鬧逾柿,春花似錦缀棍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弱匪,卻和暖如春青瀑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萧诫。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工斥难, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帘饶。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓蘸炸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親尖奔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搭儒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354