[Asp.net core series] 15 Custom Identity

0. Preface

In the previous article, I briefly introduced the Identity in asp.net core. This article will continue to further expand on Identity.

image

1. Add additional information to Identity

In the article "[asp.net core series] 13 Identity Verification Introduction", we roughly understand how to use Identity and how to save some information for subsequent verification. Here we will discuss in depth how to add more information to Identity.

We know that when adding data to Identity, we need to add a Claim object. Let's review the claim information first. Most of the attributes of Claim only provide public get accessors, so the focus of this class is on the construction method:

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

Let's take a look at a few constructor parameters that use character types:

  1. The type of type Claim supports customization, but asp.net core provides a basic type definition:

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

This class defines the types of Claims that will be used in most cases.

  1. value stores the value of Claim, and normally this value is not constrained

  2. valueType represents the type of value, and the value range refers to the class:

    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 is used to store the issuer of the claim. The default value is: ClaimsIdentity.DefaultIssuer this value allows NULL to be passed in the constructor. Once NULL is passed, it is automatically considered to beClaimsIdentity.DefaultIssuer

  4. If the original issuer of the originalIssuer Claim does not give a value, it will default to the issuer.

This is obtained from the constructor and related documentation.

About ClaimTypes, I only posted two, because these two values ​​are two indispensable values ​​in Claim. It can be seen from the attribute name, one is to set the user's name, and the other is to set the user's role.

So, continue to explore the attributes and methods in 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);
}

Several basic properties are obtained from the constructor, so I won't introduce them too much here. However, it is worth noting that the value of the Properties property needs to be used

public Claim(BinaryReader reader, ClaimsIdentity? subject)

This construction method can effectively assign a value to it, so this attribute does not have much to pay attention to.

After introducing the Claim class, let's continue to look at the commonly used classes related to 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; }

Through this method, all the Claim objects in the ClaimPrincipal can be obtained, which is an iterator object.

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

Filter out the set of claims that meet the conditions through a selector.

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

Query all Claim objects that match the type.

public virtual Claim FindFirst(string type);

Find the first Claim object whose Type value is the same as the specified value.

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

Query whether there are claim objects that meet the conditions.

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

Query whether there is a Claim object whose Type and Value properties are equal to the specified value.

These methods are all in ClaimPrincipal, and similar methods are also provided in the corresponding ClaimIdentity. I won't introduce them here.

3. Summary

This chapter introduces how to use Claim to save user information, as well as some regular usage logic. In the next chapter, we will continue to explore how to use our own Identity to achieve our goals.


Guess you like

Origin blog.51cto.com/15060511/2639805