AspNetCore 认证 <1.0>

概览

认证(Authentication):用来给用户颁发一个凭证,其包含一个用户的基本信息,这个凭证可以由第三方机构(STS)颁发,也可以由自己颁发。

认证就涉及到两个模型:票据模型和认证模型。

票据模型抽象出用户凭证

认证模型通过中间件的方式给用户颁发凭证。

票据模型

模型概览

image-20220406135609816

Claim:声明

ClaimsIdentity:身份,包含多个声明

ClaimPrincipal:用户,包含多个身份

AuthenticationTicket:票据,封装用户

TicketDataFormat:对票据进行处理,通过IDataProtector:对票据进行加密解密,通过TicketSerializer对票据进行序列化和反序列化。

Claim

陈述/声明

身份确认之后,认证方赋予的陈述,可以携带任何与认证用户相关的信息,可序列化,在网络中传递。

要点:

  • Type属性:陈述的类型
  • Value属性:陈述的值
  • ValueType属性:陈述值的类型
  • Issuer属性:颁发者
  • OriginalIssure属性:原始颁发者
  • Subject属性:陈述主题的ClaimsIdnetity对象
    /// <summary>
    /// A Claim is a statement about an entity by an Issuer.
    /// A Claim consists of a Type, Value, a Subject and an Issuer.
    /// Additional properties, ValueType, Properties and OriginalIssuer help understand the claim when making decisions.
    /// </summary>
    public class Claim
    {
        private enum SerializationMask
        {
            None = 0,
            NameClaimType = 1,
            RoleClaimType = 2,
            StringType = 4,
            Issuer = 8,
            OriginalIssuerEqualsIssuer = 16,
            OriginalIssuer = 32,
            HasProperties = 64,
            UserData = 128,
        }

        private readonly byte[]? _userSerializationData;

        private readonly string _issuer;
        private readonly string _originalIssuer;
        private Dictionary<string, string>? _properties;

        private readonly ClaimsIdentity? _subject;
        private readonly string _type;
        private readonly string _value;
        private readonly string _valueType;

        /// <summary>
        /// Initializes an instance of <see cref="Claim"/> using a <see cref="BinaryReader"/>.
        /// Normally the <see cref="BinaryReader"/> is constructed using the bytes from <see cref="WriteTo(BinaryWriter)"/> and initialized in the same way as the <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="reader">a <see cref="BinaryReader"/> pointing to a <see cref="Claim"/>.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        public Claim(BinaryReader reader)
            : this(reader, null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="Claim"/> using a <see cref="BinaryReader"/>.
        /// Normally the <see cref="BinaryReader"/> is constructed using the bytes from <see cref="WriteTo(BinaryWriter)"/> and initialized in the same way as the <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="reader">a <see cref="BinaryReader"/> pointing to a <see cref="Claim"/>.</param>
        /// <param name="subject"> the value for <see cref="Claim.Subject"/>, which is the <see cref="ClaimsIdentity"/> that has these claims.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        public Claim(BinaryReader reader, ClaimsIdentity? subject)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            _subject = subject;

            SerializationMask mask = (SerializationMask)reader.ReadInt32();
            int numPropertiesRead = 1;
            int numPropertiesToRead = reader.ReadInt32();
            _value = reader.ReadString();

            if ((mask & SerializationMask.NameClaimType) == SerializationMask.NameClaimType)
            {
                _type = ClaimsIdentity.DefaultNameClaimType;
            }
            else if ((mask & SerializationMask.RoleClaimType) == SerializationMask.RoleClaimType)
            {
                _type = ClaimsIdentity.DefaultRoleClaimType;
            }
            else
            {
                _type = reader.ReadString();
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.StringType) == SerializationMask.StringType)
            {
                _valueType = reader.ReadString();
                numPropertiesRead++;
            }
            else
            {
                _valueType = ClaimValueTypes.String;
            }

            if ((mask & SerializationMask.Issuer) == SerializationMask.Issuer)
            {
                _issuer = reader.ReadString();
                numPropertiesRead++;
            }
            else
            {
                _issuer = ClaimsIdentity.DefaultIssuer;
            }

            if ((mask & SerializationMask.OriginalIssuerEqualsIssuer) == SerializationMask.OriginalIssuerEqualsIssuer)
            {
                _originalIssuer = _issuer;
            }
            else if ((mask & SerializationMask.OriginalIssuer) == SerializationMask.OriginalIssuer)
            {
                _originalIssuer = reader.ReadString();
                numPropertiesRead++;
            }
            else
            {
                _originalIssuer = ClaimsIdentity.DefaultIssuer;
            }

            if ((mask & SerializationMask.HasProperties) == SerializationMask.HasProperties)
            {
                int numProperties = reader.ReadInt32();
                numPropertiesRead++;
                for (int i = 0; i < numProperties; i++)
                {
                    Properties.Add(reader.ReadString(), reader.ReadString());
                }
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                int cb = reader.ReadInt32();
                _userSerializationData = reader.ReadBytes(cb);
                numPropertiesRead++;
            }

            for (int i = numPropertiesRead; i < numPropertiesToRead; i++)
            {
                reader.ReadString();
            }
        }

        /// <summary>
        /// Creates a <see cref="Claim"/> with the specified type and value.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="value"/> is null.</exception>
        /// <remarks>
        /// <see cref="Claim.Issuer"/> is set to <see cref="ClaimsIdentity.DefaultIssuer"/>,
        /// <see cref="Claim.ValueType"/> is set to <see cref="ClaimValueTypes.String"/>,
        /// <see cref="Claim.OriginalIssuer"/> is set to <see cref="ClaimsIdentity.DefaultIssuer"/>, and
        /// <see cref="Claim.Subject"/> is set to null.
        /// </remarks>
        /// <seealso cref="ClaimsIdentity"/>
        /// <seealso cref="ClaimTypes"/>
        /// <seealso cref="ClaimValueTypes"/>
        public Claim(string type, string value)
            : this(type, value, ClaimValueTypes.String, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, (ClaimsIdentity?)null)
        {
        }

        /// <summary>
        /// Creates a <see cref="Claim"/> with the specified type, value, and value type.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <param name="valueType">The claim value type.</param>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="value"/> is null.</exception>
        /// <remarks>
        /// <see cref="Claim.Issuer"/> is set to <see cref="ClaimsIdentity.DefaultIssuer"/>,
        /// <see cref="Claim.OriginalIssuer"/> is set to <see cref="ClaimsIdentity.DefaultIssuer"/>,
        /// and <see cref="Claim.Subject"/> is set to null.
        /// </remarks>
        /// <seealso cref="ClaimsIdentity"/>
        /// <seealso cref="ClaimTypes"/>
        /// <seealso cref="ClaimValueTypes"/>
        public Claim(string type, string value, string? valueType)
            : this(type, value, valueType, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, (ClaimsIdentity?)null)
        {
        }

        /// <summary>
        /// Creates a <see cref="Claim"/> with the specified type, value, value type, and issuer.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <param name="valueType">The claim value type. If this parameter is empty or null, then <see cref="ClaimValueTypes.String"/> is used.</param>
        /// <param name="issuer">The claim issuer. If this parameter is empty or null, then <see cref="ClaimsIdentity.DefaultIssuer"/> is used.</param>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="value"/> is null.</exception>
        /// <remarks>
        /// <see cref="Claim.OriginalIssuer"/> is set to value of the <paramref name="issuer"/> parameter,
        /// <see cref="Claim.Subject"/> is set to null.
        /// </remarks>
        /// <seealso cref="ClaimsIdentity"/>
        /// <seealso cref="ClaimTypes"/>
        /// <seealso cref="ClaimValueTypes"/>
        public Claim(string type, string value, string? valueType, string? issuer)
            : this(type, value, valueType, issuer, issuer, (ClaimsIdentity?)null)
        {
        }

        /// <summary>
        /// Creates a <see cref="Claim"/> with the specified type, value, value type, issuer and original issuer.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <param name="valueType">The claim value type. If this parameter is null, then <see cref="ClaimValueTypes.String"/> is used.</param>
        /// <param name="issuer">The claim issuer. If this parameter is empty or null, then <see cref="ClaimsIdentity.DefaultIssuer"/> is used.</param>
        /// <param name="originalIssuer">The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.</param>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="value"/> is null.</exception>
        /// <remarks>
        /// <see cref="Claim.Subject"/> is set to null.
        /// </remarks>
        /// <seealso cref="ClaimsIdentity"/>
        /// <seealso cref="ClaimTypes"/>
        /// <seealso cref="ClaimValueTypes"/>
        public Claim(string type, string value, string? valueType, string? issuer, string? originalIssuer)
            : this(type, value, valueType, issuer, originalIssuer, (ClaimsIdentity?)null)
        {
        }

        /// <summary>
        /// Creates a <see cref="Claim"/> with the specified type, value, value type, issuer, original issuer and subject.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <param name="valueType">The claim value type. If this parameter is null, then <see cref="ClaimValueTypes.String"/> is used.</param>
        /// <param name="issuer">The claim issuer. If this parameter is empty or null, then <see cref="ClaimsIdentity.DefaultIssuer"/> is used.</param>
        /// <param name="originalIssuer">The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.</param>
        /// <param name="subject">The subject that this claim describes.</param>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> or <paramref name="value"/> is null.</exception>
        /// <seealso cref="ClaimsIdentity"/>
        /// <seealso cref="ClaimTypes"/>
        /// <seealso cref="ClaimValueTypes"/>
        public Claim(string type, string value, string? valueType, string? issuer, string? originalIssuer, ClaimsIdentity? subject)
            : this(type, value, valueType, issuer, originalIssuer, subject, null, null)
        {
        }

        /// <summary>
        /// This internal constructor was added as a performance boost when adding claims that are found in the NTToken.
        /// We need to add a property value to distinguish DeviceClaims from UserClaims.
        /// </summary>
        /// <param name="type">The claim type.</param>
        /// <param name="value">The claim value.</param>
        /// <param name="valueType">The claim value type. If this parameter is null, then <see cref="ClaimValueTypes.String"/> is used.</param>
        /// <param name="issuer">The claim issuer. If this parameter is empty or null, then <see cref="ClaimsIdentity.DefaultIssuer"/> is used.</param>
        /// <param name="originalIssuer">The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.</param>
        /// <param name="subject">The subject that this claim describes.</param>
        /// <param name="propertyKey">This allows adding a property when adding a Claim.</param>
        /// <param name="propertyValue">The value associated with the property.</param>
        internal Claim(string type, string value, string? valueType, string? issuer, string? originalIssuer, ClaimsIdentity? subject, string? propertyKey, string? propertyValue)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

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

            _type = type;
            _value = value;
            _valueType = string.IsNullOrEmpty(valueType) ? ClaimValueTypes.String : valueType;
            _issuer = string.IsNullOrEmpty(issuer) ? ClaimsIdentity.DefaultIssuer : issuer;
            _originalIssuer = string.IsNullOrEmpty(originalIssuer) ? _issuer : originalIssuer;
            _subject = subject;

            if (propertyKey != null)
            {
                _properties = new Dictionary<string, string>();
                _properties[propertyKey] = propertyValue!;
            }
        }

        /// <summary>
        /// Copy constructor for <see cref="Claim"/>
        /// </summary>
        /// <param name="other">the <see cref="Claim"/> to copy.</param>
        /// <remarks><see cref="Claim.Subject"/>will be set to 'null'.</remarks>
        /// <exception cref="ArgumentNullException">if 'other' is null.</exception>
        protected Claim(Claim other)
            : this(other, (other == null ? (ClaimsIdentity?)null : other._subject))
        {
        }

        /// <summary>
        /// Copy constructor for <see cref="Claim"/>
        /// </summary>
        /// <param name="other">the <see cref="Claim"/> to copy.</param>
        /// <param name="subject">the <see cref="ClaimsIdentity"/> to assign to <see cref="Claim.Subject"/>.</param>
        /// <remarks><see cref="Claim.Subject"/>will be set to 'subject'.</remarks>
        /// <exception cref="ArgumentNullException">if 'other' is null.</exception>
        protected Claim(Claim other, ClaimsIdentity? subject)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));

            _issuer = other._issuer;
            _originalIssuer = other._originalIssuer;
            _subject = subject;
            _type = other._type;
            _value = other._value;
            _valueType = other._valueType;
            if (other._properties != null)
            {
                _properties = new Dictionary<string, string>(other._properties);
            }

            if (other._userSerializationData != null)
            {
                _userSerializationData = other._userSerializationData.Clone() as byte[];
            }
        }

        /// <summary>
        /// Contains any additional data provided by a derived type, typically set when calling <see cref="WriteTo(BinaryWriter, byte[])"/>.
        /// </summary>
        protected virtual byte[]? CustomSerializationData
        {
            get
            {
                return _userSerializationData;
            }
        }

        /// <summary>
        /// Gets the issuer of the <see cref="Claim"/>.
        /// </summary>
        public string Issuer
        {
            get { return _issuer; }
        }

        /// <summary>
        /// Gets the original issuer of the <see cref="Claim"/>.
        /// </summary>
        /// <remarks>
        /// When the <see cref="OriginalIssuer"/> differs from the <see cref="Issuer"/>, it means
        /// that the claim was issued by the <see cref="OriginalIssuer"/> and was re-issued
        /// by the <see cref="Issuer"/>.
        /// </remarks>
        public string OriginalIssuer
        {
            get { return _originalIssuer; }
        }

        /// <summary>
        /// Gets the collection of Properties associated with the <see cref="Claim"/>.
        /// </summary>
        public IDictionary<string, string> Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new Dictionary<string, string>();
                }
                return _properties;
            }
        }

        /// <summary>
        /// Gets the subject of the <see cref="Claim"/>.
        /// </summary>
        public ClaimsIdentity? Subject
        {
            get { return _subject; }
        }

        /// <summary>
        /// Gets the claim type of the <see cref="Claim"/>.
        /// <seealso cref="ClaimTypes"/>.
        /// </summary>
        public string Type
        {
            get { return _type; }
        }

        /// <summary>
        /// Gets the value of the <see cref="Claim"/>.
        /// </summary>
        public string Value
        {
            get { return _value; }
        }

        /// <summary>
        /// Gets the value type of the <see cref="Claim"/>.
        /// <seealso cref="ClaimValueTypes"/>
        /// </summary>
        public string ValueType
        {
            get { return _valueType; }
        }

        /// <summary>
        /// Creates a new instance <see cref="Claim"/> with values copied from this object.
        /// </summary>
        public virtual Claim Clone()
        {
            return Clone((ClaimsIdentity?)null);
        }

        /// <summary>
        /// Creates a new instance <see cref="Claim"/> with values copied from this object.
        /// </summary>
        /// <param name="identity">the value for <see cref="Claim.Subject"/>, which is the <see cref="ClaimsIdentity"/> that has these claims.</param>
        /// <remarks><see cref="Claim.Subject"/> will be set to 'identity'.</remarks>
        public virtual Claim Clone(ClaimsIdentity? identity)
        {
            return new Claim(this, identity);
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <param name="writer">the <see cref="BinaryWriter"/> to use for data storage.</param>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        public virtual void WriteTo(BinaryWriter writer)
        {
            WriteTo(writer, null);
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <param name="writer">the <see cref="BinaryWriter"/> to use for data storage.</param>
        /// <param name="userData">additional data provided by derived type.</param>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        protected virtual void WriteTo(BinaryWriter writer, byte[]? userData)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            int numberOfPropertiesWritten = 1;
            SerializationMask mask = SerializationMask.None;
            if (string.Equals(_type, ClaimsIdentity.DefaultNameClaimType))
            {
                mask |= SerializationMask.NameClaimType;
            }
            else if (string.Equals(_type, ClaimsIdentity.DefaultRoleClaimType))
            {
                mask |= SerializationMask.RoleClaimType;
            }
            else
            {
                numberOfPropertiesWritten++;
            }

            if (!string.Equals(_valueType, ClaimValueTypes.String, StringComparison.Ordinal))
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.StringType;
            }

            if (!string.Equals(_issuer, ClaimsIdentity.DefaultIssuer, StringComparison.Ordinal))
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.Issuer;
            }

            if (string.Equals(_originalIssuer, _issuer, StringComparison.Ordinal))
            {
                mask |= SerializationMask.OriginalIssuerEqualsIssuer;
            }
            else if (!string.Equals(_originalIssuer, ClaimsIdentity.DefaultIssuer))
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.OriginalIssuer;
            }

            if (_properties != null && _properties.Count > 0)
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.HasProperties;
            }

            if (userData != null && userData.Length > 0)
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.UserData;
            }

            writer.Write((int)mask);
            writer.Write(numberOfPropertiesWritten);
            writer.Write(_value);

            if (((mask & SerializationMask.NameClaimType) != SerializationMask.NameClaimType) && ((mask & SerializationMask.RoleClaimType) != SerializationMask.RoleClaimType))
            {
                writer.Write(_type);
            }

            if ((mask & SerializationMask.StringType) == SerializationMask.StringType)
            {
                writer.Write(_valueType);
            }

            if ((mask & SerializationMask.Issuer) == SerializationMask.Issuer)
            {
                writer.Write(_issuer);
            }

            if ((mask & SerializationMask.OriginalIssuer) == SerializationMask.OriginalIssuer)
            {
                writer.Write(_originalIssuer);
            }

            if ((mask & SerializationMask.HasProperties) == SerializationMask.HasProperties)
            {
                writer.Write(_properties!.Count);
                foreach (var kvp in _properties)
                {
                    writer.Write(kvp.Key);
                    writer.Write(kvp.Value);
                }
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                writer.Write(userData!.Length);
                writer.Write(userData);
            }

            writer.Flush();
        }

        /// <summary>
        /// Returns a string representation of the <see cref="Claim"/> object.
        /// </summary>
        /// <remarks>
        /// The returned string contains the values of the <see cref="Type"/> and <see cref="Value"/> properties.
        /// </remarks>
        /// <returns>The string representation of the <see cref="Claim"/> object.</returns>
        public override string ToString()
        {
            return _type + ": " + _value;
        }
    }

ClaimsIdentity

表示用户的身份,一个身份可以携带多个陈述。

IIdentity

要点

  • Name属性:身份总是具有一个确定的名字
  • IsAuthenticated属性:身份是否经过认证,只有身份经过认证的用户才是信任的
  • AuthenticationType属性:认证类型
    public interface IIdentity
    {
        // Access to the name string
        string? Name { get; }

        // Access to Authentication 'type' info
        string? AuthenticationType { get; }

        // Determine if this represents the unauthenticated identity
        bool IsAuthenticated { get; }
    }

ClaimsIdentity

携带声明的身份对象。

要点

  • DefaultIssuer = @"LOCAL AUTHORITY";
  • DefaultNameClaimType = ClaimTypes.Name;
  • DefaultRoleClaimType = ClaimTypes.Role;
  • IsAuthenticated的逻辑:return !string.IsNullOrEmpty(_authenticationType);
  • Actor和BootstrapContext:表示身份委托

image-20220406151107725

    /// <summary>
    /// An Identity that is represented by a set of claims.
    /// </summary>
    public class ClaimsIdentity : IIdentity
    {
        private enum SerializationMask
        {
            None = 0,
            AuthenticationType = 1,
            BootstrapConext = 2,
            NameClaimType = 4,
            RoleClaimType = 8,
            HasClaims = 16,
            HasLabel = 32,
            Actor = 64,
            UserData = 128,
        }

        private byte[]? _userSerializationData;
        private ClaimsIdentity? _actor;
        private string? _authenticationType;
        private object? _bootstrapContext;
        private List<List<Claim>>? _externalClaims;
        private string? _label;
        private readonly List<Claim> _instanceClaims = new List<Claim>();
        private string _nameClaimType = DefaultNameClaimType;
        private string _roleClaimType = DefaultRoleClaimType;

        public const string DefaultIssuer = @"LOCAL AUTHORITY";
        public const string DefaultNameClaimType = ClaimTypes.Name;
        public const string DefaultRoleClaimType = ClaimTypes.Role;

        // NOTE about _externalClaims.
        // GenericPrincpal and RolePrincipal set role claims here so that .IsInRole will be consistent with a 'role' claim found by querying the identity or principal.
        // _externalClaims are external to the identity and assumed to be dynamic, they not serialized or copied through Clone().
        // Access through public method: ClaimProviders.

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        public ClaimsIdentity()
            : this((IIdentity?)null, (IEnumerable<Claim>?)null, (string?)null, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="identity"><see cref="IIdentity"/> supplies the <see cref="Name"/> and <see cref="AuthenticationType"/>.</param>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        public ClaimsIdentity(IIdentity? identity)
            : this(identity, (IEnumerable<Claim>?)null, (string?)null, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="claims"><see cref="IEnumerable{Claim}"/> associated with this instance.</param>
        /// <remarks>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        /// </remarks>
        public ClaimsIdentity(IEnumerable<Claim>? claims)
            : this((IIdentity?)null, claims, (string?)null, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="authenticationType">The authentication method used to establish this identity.</param>
        public ClaimsIdentity(string? authenticationType)
            : this((IIdentity?)null, (IEnumerable<Claim>?)null, authenticationType, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="claims"><see cref="IEnumerable{Claim}"/> associated with this instance.</param>
        /// <param name="authenticationType">The authentication method used to establish this identity.</param>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        public ClaimsIdentity(IEnumerable<Claim>? claims, string? authenticationType)
            : this((IIdentity?)null, claims, authenticationType, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="identity"><see cref="IIdentity"/> supplies the <see cref="Name"/> and <see cref="AuthenticationType"/>.</param>
        /// <param name="claims"><see cref="IEnumerable{Claim}"/> associated with this instance.</param>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        public ClaimsIdentity(IIdentity? identity, IEnumerable<Claim>? claims)
            : this(identity, claims, (string?)null, (string?)null, (string?)null)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="authenticationType">The type of authentication used.</param>
        /// <param name="nameType">The <see cref="Claim.Type"/> used when obtaining the value of <see cref="ClaimsIdentity.Name"/>.</param>
        /// <param name="roleType">The <see cref="Claim.Type"/> used when performing logic for <see cref="ClaimsPrincipal.IsInRole"/>.</param>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        public ClaimsIdentity(string? authenticationType, string? nameType, string? roleType)
            : this((IIdentity?)null, (IEnumerable<Claim>?)null, authenticationType, nameType, roleType)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="claims"><see cref="IEnumerable{Claim}"/> associated with this instance.</param>
        /// <param name="authenticationType">The type of authentication used.</param>
        /// <param name="nameType">The <see cref="Claim.Type"/> used when obtaining the value of <see cref="ClaimsIdentity.Name"/>.</param>
        /// <param name="roleType">The <see cref="Claim.Type"/> used when performing logic for <see cref="ClaimsPrincipal.IsInRole"/>.</param>
        /// <remarks><seealso cref="ClaimsIdentity(IIdentity, IEnumerable{Claim}, string, string, string)"/> for details on how internal values are set.</remarks>
        public ClaimsIdentity(IEnumerable<Claim>? claims, string? authenticationType, string? nameType, string? roleType)
            : this((IIdentity?)null, claims, authenticationType, nameType, roleType)
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="identity"><see cref="IIdentity"/> supplies the <see cref="Name"/> and <see cref="AuthenticationType"/>.</param>
        /// <param name="claims"><see cref="IEnumerable{Claim}"/> associated with this instance.</param>
        /// <param name="authenticationType">The type of authentication used.</param>
        /// <param name="nameType">The <see cref="Claim.Type"/> used when obtaining the value of <see cref="ClaimsIdentity.Name"/>.</param>
        /// <param name="roleType">The <see cref="Claim.Type"/> used when performing logic for <see cref="ClaimsPrincipal.IsInRole"/>.</param>
        /// <remarks>If 'identity' is a <see cref="ClaimsIdentity"/>, then there are potentially multiple sources for AuthenticationType, NameClaimType, RoleClaimType.
        /// <para>Priority is given to the parameters: authenticationType, nameClaimType, roleClaimType.</para>
        /// <para>All <see cref="Claim"/>s are copied into this instance in a <see cref="List{Claim}"/>. Each Claim is examined and if Claim.Subject != this, then Claim.Clone(this) is called before the claim is added.</para>
        /// <para>Any 'External' claims are ignored.</para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">if 'identity' is a <see cref="ClaimsIdentity"/> and <see cref="ClaimsIdentity.Actor"/> results in a circular reference back to 'this'.</exception>
        public ClaimsIdentity(IIdentity? identity, IEnumerable<Claim>? claims, string? authenticationType, string? nameType, string? roleType)
        {
            ClaimsIdentity? claimsIdentity = identity as ClaimsIdentity;

            _authenticationType = (identity != null && string.IsNullOrEmpty(authenticationType)) ? identity.AuthenticationType : authenticationType;
            _nameClaimType = !string.IsNullOrEmpty(nameType) ? nameType : (claimsIdentity != null ? claimsIdentity._nameClaimType : DefaultNameClaimType);
            _roleClaimType = !string.IsNullOrEmpty(roleType) ? roleType : (claimsIdentity != null ? claimsIdentity._roleClaimType : DefaultRoleClaimType);

            if (claimsIdentity != null)
            {
                _label = claimsIdentity._label;
                _bootstrapContext = claimsIdentity._bootstrapContext;

                if (claimsIdentity.Actor != null)
                {
                    //
                    // Check if the Actor is circular before copying. That check is done while setting
                    // the Actor property and so not really needed here. But checking just for sanity sake
                    //
                    if (!IsCircular(claimsIdentity.Actor))
                    {
                        _actor = claimsIdentity.Actor;
                    }
                    else
                    {
                        throw new InvalidOperationException(SR.InvalidOperationException_ActorGraphCircular);
                    }
                }
                SafeAddClaims(claimsIdentity._instanceClaims);
            }
            else
            {
                if (identity != null && !string.IsNullOrEmpty(identity.Name))
                {
                    SafeAddClaim(new Claim(_nameClaimType, identity.Name, ClaimValueTypes.String, DefaultIssuer, DefaultIssuer, this));
                }
            }

            if (claims != null)
            {
                SafeAddClaims(claims);
            }
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/> using a <see cref="BinaryReader"/>.
        /// Normally the <see cref="BinaryReader"/> is constructed using the bytes from <see cref="WriteTo(BinaryWriter)"/> and initialized in the same way as the <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="reader">a <see cref="BinaryReader"/> pointing to a <see cref="ClaimsIdentity"/>.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        public ClaimsIdentity(BinaryReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException(nameof(reader));

            Initialize(reader);
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="other"><see cref="ClaimsIdentity"/> to copy.</param>
        /// <exception cref="ArgumentNullException">if 'other' is null.</exception>
        protected ClaimsIdentity(ClaimsIdentity other)
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            if (other._actor != null)
            {
                _actor = other._actor.Clone();
            }

            _authenticationType = other._authenticationType;
            _bootstrapContext = other._bootstrapContext;
            _label = other._label;
            _nameClaimType = other._nameClaimType;
            _roleClaimType = other._roleClaimType;
            if (other._userSerializationData != null)
            {
                _userSerializationData = other._userSerializationData.Clone() as byte[];
            }

            SafeAddClaims(other._instanceClaims);
        }

        protected ClaimsIdentity(SerializationInfo info, StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsIdentity"/> from a serialized stream created via
        /// <see cref="ISerializable"/>.
        /// </summary>
        /// <param name="info">
        /// The <see cref="SerializationInfo"/> to read from.
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown is the <paramref name="info"/> is null.</exception>
        protected ClaimsIdentity(SerializationInfo info)
        {
            throw new PlatformNotSupportedException();
        }

        /// <summary>
        /// Gets the authentication type that can be used to determine how this <see cref="ClaimsIdentity"/> authenticated to an authority.
        /// </summary>
        public virtual string? AuthenticationType
        {
            get { return _authenticationType; }
        }

        /// <summary>
        /// Gets a value that indicates if the user has been authenticated.
        /// </summary>
        public virtual bool IsAuthenticated
        {
            get { return !string.IsNullOrEmpty(_authenticationType); }
        }

        /// <summary>
        /// Gets or sets a <see cref="ClaimsIdentity"/> that was granted delegation rights.
        /// </summary>
        /// <exception cref="InvalidOperationException">if 'value' results in a circular reference back to 'this'.</exception>
        public ClaimsIdentity? Actor
        {
            get { return _actor; }
            set
            {
                if (value != null)
                {
                    if (IsCircular(value))
                    {
                        throw new InvalidOperationException(SR.InvalidOperationException_ActorGraphCircular);
                    }
                }
                _actor = value;
            }
        }

        /// <summary>
        /// Gets or sets a context that was used to create this <see cref="ClaimsIdentity"/>.
        /// </summary>
        public object? BootstrapContext
        {
            get { return _bootstrapContext; }
            set { _bootstrapContext = value; }
        }

        /// <summary>
        /// Gets the claims as <see cref="IEnumerable{Claim}"/>, associated with this <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <remarks>May contain nulls.</remarks>
        public virtual IEnumerable<Claim> Claims
        {
            get
            {
                if (_externalClaims == null)
                {
                    return _instanceClaims;
                }

                return CombinedClaimsIterator();
            }
        }

        private IEnumerable<Claim> CombinedClaimsIterator()
        {
            for (int i = 0; i < _instanceClaims.Count; i++)
            {
                yield return _instanceClaims[i];
            }

            for (int j = 0; j < _externalClaims!.Count; j++)
            {
                if (_externalClaims[j] != null)
                {
                    foreach (Claim claim in _externalClaims[j])
                    {
                        yield return claim;
                    }
                }
            }
        }

        /// <summary>
        /// Contains any additional data provided by a derived type, typically set when calling <see cref="WriteTo(BinaryWriter, byte[])"/>.
        /// </summary>
        protected virtual byte[]? CustomSerializationData
        {
            get
            {
                return _userSerializationData;
            }
        }

        /// <summary>
        /// Allow the association of claims with this instance of <see cref="ClaimsIdentity"/>.
        /// The claims will not be serialized or added in Clone(). They will be included in searches, finds and returned from the call to <see cref="ClaimsIdentity.Claims"/>.
        /// </summary>
        internal List<List<Claim>> ExternalClaims
        {
            get
            {
                if (_externalClaims == null)
                {
                    _externalClaims = new List<List<Claim>>();
                }
                return _externalClaims;
            }
        }

        /// <summary>
        /// Gets or sets the label for this <see cref="ClaimsIdentity"/>
        /// </summary>
        public string? Label
        {
            get { return _label; }
            set { _label = value; }
        }

        /// <summary>
        /// Gets the Name of this <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <remarks>Calls <see cref="FindFirst(string)"/> where string == NameClaimType, if found, returns <see cref="Claim.Value"/> otherwise null.</remarks>
        public virtual string? Name
        {
            // just an accessor for getting the name claim
            get
            {
                Claim? claim = FindFirst(_nameClaimType);
                if (claim != null)
                {
                    return claim.Value;
                }

                return null;
            }
        }

        /// <summary>
        /// Gets the value that identifies 'Name' claims. This is used when returning the property <see cref="ClaimsIdentity.Name"/>.
        /// </summary>
        public string NameClaimType
        {
            get { return _nameClaimType; }
        }

        /// <summary>
        /// Gets the value that identifies 'Role' claims. This is used when calling <see cref="ClaimsPrincipal.IsInRole"/>.
        /// </summary>
        public string RoleClaimType
        {
            get { return _roleClaimType; }
        }

        /// <summary>
        /// Creates a new instance of <see cref="ClaimsIdentity"/> with values copied from this object.
        /// </summary>
        public virtual ClaimsIdentity Clone()
        {
            return new ClaimsIdentity(this);
        }

        /// <summary>
        /// Adds a single <see cref="Claim"/> to an internal list.
        /// </summary>
        /// <param name="claim">the <see cref="Claim"/>add.</param>
        /// <remarks>If <see cref="Claim.Subject"/> != this, then Claim.Clone(this) is called before the claim is added.</remarks>
        /// <exception cref="ArgumentNullException">if 'claim' is null.</exception>
        public virtual void AddClaim(Claim claim)
        {
            if (claim == null)
            {
                throw new ArgumentNullException(nameof(claim));
            }

            if (object.ReferenceEquals(claim.Subject, this))
            {
                _instanceClaims.Add(claim);
            }
            else
            {
                _instanceClaims.Add(claim.Clone(this));
            }
        }

        /// <summary>
        /// Adds a <see cref="IEnumerable{Claim}"/> to the internal list.
        /// </summary>
        /// <param name="claims">Enumeration of claims to add.</param>
        /// <remarks>Each claim is examined and if <see cref="Claim.Subject"/> != this, then Claim.Clone(this) is called before the claim is added.</remarks>
        /// <exception cref="ArgumentNullException">if 'claims' is null.</exception>
        public virtual void AddClaims(IEnumerable<Claim?> claims)
        {
            if (claims == null)
            {
                throw new ArgumentNullException(nameof(claims));
            }

            foreach (Claim? claim in claims)
            {
                if (claim == null)
                {
                    continue;
                }

                if (object.ReferenceEquals(claim.Subject, this))
                {
                    _instanceClaims.Add(claim);
                }
                else
                {
                    _instanceClaims.Add(claim.Clone(this));
                }
            }
        }

        /// <summary>
        /// Attempts to remove a <see cref="Claim"/> the internal list.
        /// </summary>
        /// <param name="claim">the <see cref="Claim"/> to match.</param>
        /// <remarks> It is possible that a <see cref="Claim"/> returned from <see cref="Claims"/> cannot be removed. This would be the case for 'External' claims that are provided by reference.
        /// <para>object.ReferenceEquals is used to 'match'.</para>
        /// </remarks>
        public virtual bool TryRemoveClaim(Claim? claim)
        {
            if (claim == null)
            {
                return false;
            }

            bool removed = false;

            for (int i = 0; i < _instanceClaims.Count; i++)
            {
                if (object.ReferenceEquals(_instanceClaims[i], claim))
                {
                    _instanceClaims.RemoveAt(i);
                    removed = true;
                    break;
                }
            }
            return removed;
        }

        /// <summary>
        /// Removes a <see cref="Claim"/> from the internal list.
        /// </summary>
        /// <param name="claim">the <see cref="Claim"/> to match.</param>
        /// <remarks> It is possible that a <see cref="Claim"/> returned from <see cref="Claims"/> cannot be removed. This would be the case for 'External' claims that are provided by reference.
        /// <para>object.ReferenceEquals is used to 'match'.</para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">if 'claim' cannot be removed.</exception>
        public virtual void RemoveClaim(Claim? claim)
        {
            if (!TryRemoveClaim(claim))
            {
                throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ClaimCannotBeRemoved, claim));
            }
        }

        /// <summary>
        /// Adds claims to internal list. Calling Claim.Clone if Claim.Subject != this.
        /// </summary>
        /// <param name="claims">a <see cref="IEnumerable{Claim}"/> to add to </param>
        /// <remarks>private only call from constructor, adds to internal list.</remarks>
        private void SafeAddClaims(IEnumerable<Claim?> claims)
        {
            foreach (Claim? claim in claims)
            {
                if (claim == null)
                    continue;

                if (object.ReferenceEquals(claim.Subject, this))
                {
                    _instanceClaims.Add(claim);
                }
                else
                {
                    _instanceClaims.Add(claim.Clone(this));
                }
            }
        }

        /// <summary>
        /// Adds claim to internal list. Calling Claim.Clone if Claim.Subject != this.
        /// </summary>
        /// <remarks>private only call from constructor, adds to internal list.</remarks>
        private void SafeAddClaim(Claim? claim)
        {
            if (claim == null)
                return;

            if (object.ReferenceEquals(claim.Subject, this))
            {
                _instanceClaims.Add(claim);
            }
            else
            {
                _instanceClaims.Add(claim.Clone(this));
            }
        }

        /// <summary>
        /// Retrieves a <see cref="IEnumerable{Claim}"/> where each claim is matched by <paramref name="match"/>.
        /// </summary>
        /// <param name="match">The function that performs the matching logic.</param>
        /// <returns>A <see cref="IEnumerable{Claim}"/> of matched claims.</returns>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            foreach (Claim claim in Claims)
            {
                if (match(claim))
                {
                    yield return claim;
                }
            }
        }

        /// <summary>
        /// Retrieves a <see cref="IEnumerable{Claim}"/> where each Claim.Type equals <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The type of the claim to match.</param>
        /// <returns>A <see cref="IEnumerable{Claim}"/> of matched claims.</returns>
        /// <remarks>Comparison is: StringComparison.OrdinalIgnoreCase.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        public virtual IEnumerable<Claim> FindAll(string type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            foreach (Claim claim in Claims)
            {
                if (claim != null)
                {
                    if (string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase))
                    {
                        yield return claim;
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves the first <see cref="Claim"/> that is matched by <paramref name="match"/>.
        /// </summary>
        /// <param name="match">The function that performs the matching logic.</param>
        /// <returns>A <see cref="Claim"/>, null if nothing matches.</returns>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual Claim? FindFirst(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            foreach (Claim claim in Claims)
            {
                if (match(claim))
                {
                    return claim;
                }
            }

            return null;
        }

        /// <summary>
        /// Retrieves the first <see cref="Claim"/> where Claim.Type equals <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The type of the claim to match.</param>
        /// <returns>A <see cref="Claim"/>, null if nothing matches.</returns>
        /// <remarks>Comparison is: StringComparison.OrdinalIgnoreCase.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        public virtual Claim? FindFirst(string type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            foreach (Claim claim in Claims)
            {
                if (claim != null)
                {
                    if (string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase))
                    {
                        return claim;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Determines if a claim is contained within this ClaimsIdentity.
        /// </summary>
        /// <param name="match">The function that performs the matching logic.</param>
        /// <returns>true if a claim is found, false otherwise.</returns>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual bool HasClaim(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            foreach (Claim claim in Claims)
            {
                if (match(claim))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if a claim with type AND value is contained within this ClaimsIdentity.
        /// </summary>
        /// <param name="type">the type of the claim to match.</param>
        /// <param name="value">the value of the claim to match.</param>
        /// <returns>true if a claim is matched, false otherwise.</returns>
        /// <remarks>Comparison is: StringComparison.OrdinalIgnoreCase for Claim.Type, StringComparison.Ordinal for Claim.Value.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        /// <exception cref="ArgumentNullException">if 'value' is null.</exception>
        public virtual bool HasClaim(string type, string value)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

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

            foreach (Claim claim in Claims)
            {
                if (claim != null
                        && string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase)
                        && string.Equals(claim.Value, value, StringComparison.Ordinal))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Initializes from a <see cref="BinaryReader"/>. Normally the reader is initialized with the results from <see cref="WriteTo(BinaryWriter)"/>
        /// Normally the <see cref="BinaryReader"/> is initialized in the same way as the <see cref="BinaryWriter"/> passed to <see cref="WriteTo(BinaryWriter)"/>.
        /// </summary>
        /// <param name="reader">a <see cref="BinaryReader"/> pointing to a <see cref="ClaimsIdentity"/>.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        private void Initialize(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            SerializationMask mask = (SerializationMask)reader.ReadInt32();
            int numPropertiesRead = 0;
            int numPropertiesToRead = reader.ReadInt32();

            if ((mask & SerializationMask.AuthenticationType) == SerializationMask.AuthenticationType)
            {
                _authenticationType = reader.ReadString();
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.BootstrapConext) == SerializationMask.BootstrapConext)
            {
                _bootstrapContext = reader.ReadString();
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.NameClaimType) == SerializationMask.NameClaimType)
            {
                _nameClaimType = reader.ReadString();
                numPropertiesRead++;
            }
            else
            {
                _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
            }

            if ((mask & SerializationMask.RoleClaimType) == SerializationMask.RoleClaimType)
            {
                _roleClaimType = reader.ReadString();
                numPropertiesRead++;
            }
            else
            {
                _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;
            }

            if ((mask & SerializationMask.HasLabel) == SerializationMask.HasLabel)
            {
                _label = reader.ReadString();
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.HasClaims) == SerializationMask.HasClaims)
            {
                int numberOfClaims = reader.ReadInt32();
                for (int index = 0; index < numberOfClaims; index++)
                {
                    _instanceClaims.Add(CreateClaim(reader));
                }
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.Actor) == SerializationMask.Actor)
            {
                _actor = new ClaimsIdentity(reader);
                numPropertiesRead++;
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                int cb = reader.ReadInt32();
                _userSerializationData = reader.ReadBytes(cb);
                numPropertiesRead++;
            }

            for (int i = numPropertiesRead; i < numPropertiesToRead; i++)
            {
                reader.ReadString();
            }
        }

        /// <summary>
        /// Provides an extensibility point for derived types to create a custom <see cref="Claim"/>.
        /// </summary>
        /// <param name="reader">the <see cref="BinaryReader"/>that points at the claim.</param>
        /// <returns>a new <see cref="Claim"/>.</returns>
        protected virtual Claim CreateClaim(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            return new Claim(reader, this);
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <param name="writer">the <see cref="BinaryWriter"/> to use for data storage.</param>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        public virtual void WriteTo(BinaryWriter writer)
        {
            WriteTo(writer, null);
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <param name="writer">the <see cref="BinaryWriter"/> to use for data storage.</param>
        /// <param name="userData">additional data provided by derived type.</param>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        protected virtual void WriteTo(BinaryWriter writer, byte[]? userData)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            int numberOfPropertiesWritten = 0;
            var mask = SerializationMask.None;
            if (_authenticationType != null)
            {
                mask |= SerializationMask.AuthenticationType;
                numberOfPropertiesWritten++;
            }

            if (_bootstrapContext != null)
            {
                if (_bootstrapContext is string rawData)
                {
                    mask |= SerializationMask.BootstrapConext;
                    numberOfPropertiesWritten++;
                }
            }

            if (!string.Equals(_nameClaimType, ClaimsIdentity.DefaultNameClaimType, StringComparison.Ordinal))
            {
                mask |= SerializationMask.NameClaimType;
                numberOfPropertiesWritten++;
            }

            if (!string.Equals(_roleClaimType, ClaimsIdentity.DefaultRoleClaimType, StringComparison.Ordinal))
            {
                mask |= SerializationMask.RoleClaimType;
                numberOfPropertiesWritten++;
            }

            if (!string.IsNullOrWhiteSpace(_label))
            {
                mask |= SerializationMask.HasLabel;
                numberOfPropertiesWritten++;
            }

            if (_instanceClaims.Count > 0)
            {
                mask |= SerializationMask.HasClaims;
                numberOfPropertiesWritten++;
            }

            if (_actor != null)
            {
                mask |= SerializationMask.Actor;
                numberOfPropertiesWritten++;
            }

            if (userData != null && userData.Length > 0)
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.UserData;
            }

            writer.Write((int)mask);
            writer.Write(numberOfPropertiesWritten);
            if ((mask & SerializationMask.AuthenticationType) == SerializationMask.AuthenticationType)
            {
                writer.Write(_authenticationType!);
            }

            if ((mask & SerializationMask.BootstrapConext) == SerializationMask.BootstrapConext)
            {
                writer.Write((string)_bootstrapContext!);
            }

            if ((mask & SerializationMask.NameClaimType) == SerializationMask.NameClaimType)
            {
                writer.Write(_nameClaimType);
            }

            if ((mask & SerializationMask.RoleClaimType) == SerializationMask.RoleClaimType)
            {
                writer.Write(_roleClaimType);
            }

            if ((mask & SerializationMask.HasLabel) == SerializationMask.HasLabel)
            {
                writer.Write(_label!);
            }

            if ((mask & SerializationMask.HasClaims) == SerializationMask.HasClaims)
            {
                writer.Write(_instanceClaims.Count);
                foreach (var claim in _instanceClaims)
                {
                    claim.WriteTo(writer);
                }
            }

            if ((mask & SerializationMask.Actor) == SerializationMask.Actor)
            {
                _actor!.WriteTo(writer);
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                writer.Write(userData!.Length);
                writer.Write(userData);
            }

            writer.Flush();
        }

        /// <summary>
        /// Checks if a circular reference exists to 'this'
        /// </summary>
        /// <param name="subject"></param>
        /// <returns></returns>
        private bool IsCircular(ClaimsIdentity subject)
        {
            if (ReferenceEquals(this, subject))
            {
                return true;
            }

            ClaimsIdentity currSubject = subject;

            while (currSubject.Actor != null)
            {
                if (ReferenceEquals(this, currSubject.Actor))
                {
                    return true;
                }

                currSubject = currSubject.Actor;
            }

            return false;
        }

        /// <summary>
        /// Populates the specified <see cref="SerializationInfo"/> with the serialization data for the ClaimsIdentity
        /// </summary>
        /// <param name="info">The serialization information stream to write to. Satisfies ISerializable contract.</param>
        /// <param name="context">Context for serialization. Can be null.</param>
        /// <exception cref="ArgumentNullException">Thrown if the info parameter is null.</exception>
        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }
    }

GenericIdentity

要点:

  • IsAuthenticated的内部逻辑:!m_name.Equals("");
    public class GenericIdentity : ClaimsIdentity
    {
        private readonly string m_name;
        private readonly string m_type;

        public GenericIdentity(string name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));

            m_name = name;
            m_type = "";

            AddNameClaim();
        }

        public GenericIdentity(string name, string type)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            if (type == null)
                throw new ArgumentNullException(nameof(type));

            m_name = name;
            m_type = type;

            AddNameClaim();
        }

        protected GenericIdentity(GenericIdentity identity)
            : base(identity)
        {
            m_name = identity.m_name;
            m_type = identity.m_type;
        }

        /// <summary>
        /// Returns a new instance of <see cref="GenericIdentity"/> with values copied from this object.
        /// </summary>
        public override ClaimsIdentity Clone()
        {
            return new GenericIdentity(this);
        }

        public override IEnumerable<Claim> Claims
        {
            get
            {
                return base.Claims;
            }
        }

        public override string Name
        {
            get
            {
                return m_name;
            }
        }

        public override string AuthenticationType
        {
            get
            {
                return m_type;
            }
        }

        public override bool IsAuthenticated
        {
            get
            {
                return !m_name.Equals("");
            }
        }

        private void AddNameClaim()
        {
            if (m_name != null)
            {
                base.AddClaim(new Claim(base.NameClaimType, m_name, ClaimValueTypes.String, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, this));
            }
        }
    }

ClaimsPrincipal

用来抽象用户

IPrincipal

要点

  • 认证用户必须由一个身份
  • IsInRole:确定当前用户是否被添加到指定角色中。如果是基于用户角色的授权方式,可以直接调用这个方法决定当前用户是否具有访问目标资源的权限。
    public interface IPrincipal
    {
        // Retrieve the identity object
        IIdentity? Identity { get; }

        // Perform a check for a specific role
        bool IsInRole(string role);
    }

ClaimsPrincipal

要点

  • IsInRole内部逻辑:this._identities[index].HasClaim(this._identities[index].RoleClaimType, role

  • 多个身份会有一个首要身份,使用Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity?> SelectPrimaryIdentity来进行查找

  •     /// <summary>
        /// Gets the identity of the current principal.
        /// </summary>
        public virtual System.Security.Principal.IIdentity? Identity
        {
            get
            {
                if (s_identitySelector != null)
                {
                    return s_identitySelector(_identities);
                }
                else
                {
                    return SelectPrimaryIdentity(_identities);
                }
            }
        }
        /// <summary>
        /// This method iterates through the collection of ClaimsIdentities and chooses an identity as the primary.
        /// </summary>
        private static ClaimsIdentity? SelectPrimaryIdentity(IEnumerable<ClaimsIdentity> identities)
        {
            if (identities == null)
            {
                throw new ArgumentNullException(nameof(identities));
            }
        
            foreach (ClaimsIdentity identity in identities)
            {
                if (identity != null)
                {
                    return identity;
                }
            }
        
            return null;
        }
    

image-20220406151012328

    /// <summary>
    /// Concrete IPrincipal supporting multiple claims-based identities
    /// </summary>
    public class ClaimsPrincipal : IPrincipal
    {
        private enum SerializationMask
        {
            None = 0,
            HasIdentities = 1,
            UserData = 2
        }

        private readonly List<ClaimsIdentity> _identities = new List<ClaimsIdentity>();
        private readonly byte[]? _userSerializationData;

        private static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity?> s_identitySelector = SelectPrimaryIdentity;
        private static Func<ClaimsPrincipal> s_principalSelector = ClaimsPrincipalSelector;

        private static ClaimsPrincipal? SelectClaimsPrincipal()
        {
            // Diverging behavior from .NET Framework: In Framework, the default PrincipalPolicy is
            // UnauthenticatedPrincipal. In .NET Core, the default is NoPrincipal. .NET Framework
            // would throw an ArgumentNullException when constructing the ClaimsPrincipal with a
            // null principal from the thread if it were set to use NoPrincipal. In .NET Core, since
            // NoPrincipal is the default, we return null instead of throw.

            IPrincipal? threadPrincipal = Thread.CurrentPrincipal;

            return threadPrincipal switch {
                ClaimsPrincipal claimsPrincipal => claimsPrincipal,
                not null => new ClaimsPrincipal(threadPrincipal),
                null => null
            };
        }

        protected ClaimsPrincipal(SerializationInfo info, StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }

        /// <summary>
        /// This method iterates through the collection of ClaimsIdentities and chooses an identity as the primary.
        /// </summary>
        private static ClaimsIdentity? SelectPrimaryIdentity(IEnumerable<ClaimsIdentity> identities)
        {
            if (identities == null)
            {
                throw new ArgumentNullException(nameof(identities));
            }

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

            return null;
        }

        public static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity?> PrimaryIdentitySelector
        {
            get
            {
                return s_identitySelector;
            }
            set
            {
                s_identitySelector = value;
            }
        }

        public static Func<ClaimsPrincipal> ClaimsPrincipalSelector
        {
            get
            {
                return s_principalSelector;
            }
            set
            {
                s_principalSelector = value;
            }
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsPrincipal"/>.
        /// </summary>
        public ClaimsPrincipal()
        {
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsPrincipal"/>.
        /// </summary>
        /// <param name="identities"> <see cref="IEnumerable{ClaimsIdentity}"/> the subjects in the principal.</param>
        /// <exception cref="ArgumentNullException">if 'identities' is null.</exception>
        public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities)
        {
            if (identities == null)
            {
                throw new ArgumentNullException(nameof(identities));
            }

            _identities.AddRange(identities);
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsPrincipal"/>
        /// </summary>
        /// <param name="identity"> <see cref="IIdentity"/> representing the subject in the principal. </param>
        /// <exception cref="ArgumentNullException">if 'identity' is null.</exception>
        public ClaimsPrincipal(IIdentity identity)
        {
            if (identity == null)
            {
                throw new ArgumentNullException(nameof(identity));
            }

            if (identity is ClaimsIdentity ci)
            {
                _identities.Add(ci);
            }
            else
            {
                _identities.Add(new ClaimsIdentity(identity));
            }
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsPrincipal"/>
        /// </summary>
        /// <param name="principal"><see cref="IPrincipal"/> used to form this instance.</param>
        /// <exception cref="ArgumentNullException">if 'principal' is null.</exception>
        public ClaimsPrincipal(IPrincipal principal)
        {
            if (null == principal)
            {
                throw new ArgumentNullException(nameof(principal));
            }

            //
            // If IPrincipal is a ClaimsPrincipal add all of the identities
            // If IPrincipal is not a ClaimsPrincipal, create a new identity from IPrincipal.Identity
            //
            ClaimsPrincipal? cp = principal as ClaimsPrincipal;
            if (null == cp)
            {
                _identities.Add(new ClaimsIdentity(principal.Identity));
            }
            else
            {
                if (null != cp.Identities)
                {
                    _identities.AddRange(cp.Identities);
                }
            }
        }

        /// <summary>
        /// Initializes an instance of <see cref="ClaimsPrincipal"/> using a <see cref="BinaryReader"/>.
        /// Normally the <see cref="BinaryReader"/> is constructed using the bytes from <see cref="WriteTo(BinaryWriter)"/> and initialized in the same way as the <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="reader">a <see cref="BinaryReader"/> pointing to a <see cref="ClaimsPrincipal"/>.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        public ClaimsPrincipal(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            SerializationMask mask = (SerializationMask)reader.ReadInt32();
            int numPropertiesToRead = reader.ReadInt32();
            int numPropertiesRead = 0;
            if ((mask & SerializationMask.HasIdentities) == SerializationMask.HasIdentities)
            {
                numPropertiesRead++;
                int numberOfIdentities = reader.ReadInt32();
                for (int index = 0; index < numberOfIdentities; ++index)
                {
                    // directly add to _identities as that is what we serialized from
                    _identities.Add(CreateClaimsIdentity(reader));
                }
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                int cb = reader.ReadInt32();
                _userSerializationData = reader.ReadBytes(cb);
                numPropertiesRead++;
            }

            for (int i = numPropertiesRead; i < numPropertiesToRead; i++)
            {
                reader.ReadString();
            }
        }

        /// <summary>
        /// Adds a single <see cref="ClaimsIdentity"/> to an internal list.
        /// </summary>
        /// <param name="identity">the <see cref="ClaimsIdentity"/>add.</param>
        /// <exception cref="ArgumentNullException">if 'identity' is null.</exception>
        public virtual void AddIdentity(ClaimsIdentity identity)
        {
            if (identity == null)
            {
                throw new ArgumentNullException(nameof(identity));
            }

            _identities.Add(identity);
        }

        /// <summary>
        /// Adds a <see cref="IEnumerable{ClaimsIdentity}"/> to the internal list.
        /// </summary>
        /// <param name="identities">Enumeration of ClaimsIdentities to add.</param>
        /// <exception cref="ArgumentNullException">if 'identities' is null.</exception>
        public virtual void AddIdentities(IEnumerable<ClaimsIdentity> identities)
        {
            if (identities == null)
            {
                throw new ArgumentNullException(nameof(identities));
            }

            _identities.AddRange(identities);
        }

        /// <summary>
        /// Gets the claims as <see cref="IEnumerable{Claim}"/>, associated with this <see cref="ClaimsPrincipal"/> by enumerating all <see cref="Identities"/>.
        /// </summary>
        public virtual IEnumerable<Claim> Claims
        {
            get
            {
                foreach (ClaimsIdentity identity in Identities)
                {
                    foreach (Claim claim in identity.Claims)
                    {
                        yield return claim;
                    }
                }
            }
        }

        /// <summary>
        /// Contains any additional data provided by derived type, typically set when calling <see cref="WriteTo(BinaryWriter, byte[])"/>.
        /// </summary>
        protected virtual byte[]? CustomSerializationData
        {
            get
            {
                return _userSerializationData;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="ClaimsPrincipal"/> with values copied from this object.
        /// </summary>
        public virtual ClaimsPrincipal Clone()
        {
            return new ClaimsPrincipal(this);
        }

        /// <summary>
        /// Provides an extensibility point for derived types to create a custom <see cref="ClaimsIdentity"/>.
        /// </summary>
        /// <param name="reader">the <see cref="BinaryReader"/>that points at the claim.</param>
        /// <exception cref="ArgumentNullException">if 'reader' is null.</exception>
        /// <returns>a new <see cref="ClaimsIdentity"/>.</returns>
        protected virtual ClaimsIdentity CreateClaimsIdentity(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            return new ClaimsIdentity(reader);
        }

        /// <summary>
        /// Returns the Current Principal by calling a delegate.  Users may specify the delegate.
        /// </summary>
        public static ClaimsPrincipal? Current
        {
            // just accesses the current selected principal selector, doesn't set
            get
            {
                return s_principalSelector is not null ? s_principalSelector() : SelectClaimsPrincipal();
            }
        }

        /// <summary>
        /// Retrieves a <see cref="IEnumerable{Claim}"/> where each claim is matched by <paramref name="match"/>.
        /// </summary>
        /// <param name="match">The predicate that performs the matching logic.</param>
        /// <returns>A <see cref="IEnumerable{Claim}"/> of matched claims.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.FindAll(string)"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            foreach (ClaimsIdentity identity in Identities)
            {
                if (identity != null)
                {
                    foreach (Claim claim in identity.FindAll(match))
                    {
                        yield return claim;
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves a <see cref="IEnumerable{Claim}"/> where each Claim.Type equals <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The type of the claim to match.</param>
        /// <returns>A <see cref="IEnumerable{Claim}"/> of matched claims.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.FindAll(Predicate{Claim})"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        public virtual IEnumerable<Claim> FindAll(string type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            foreach (ClaimsIdentity identity in Identities)
            {
                if (identity != null)
                {
                    foreach (Claim claim in identity.FindAll(type))
                    {
                        yield return claim;
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves the first <see cref="Claim"/> that is matched by <paramref name="match"/>.
        /// </summary>
        /// <param name="match">The predicate that performs the matching logic.</param>
        /// <returns>A <see cref="Claim"/>, null if nothing matches.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.FindFirst(string)"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual Claim? FindFirst(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            Claim? claim = null;

            foreach (ClaimsIdentity identity in Identities)
            {
                if (identity != null)
                {
                    claim = identity.FindFirst(match);
                    if (claim != null)
                    {
                        return claim;
                    }
                }
            }

            return claim;
        }

        /// <summary>
        /// Retrieves the first <see cref="Claim"/> where the Claim.Type equals <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The type of the claim to match.</param>
        /// <returns>A <see cref="Claim"/>, null if nothing matches.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.FindFirst(Predicate{Claim})"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        public virtual Claim? FindFirst(string type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            Claim? claim = null;

            for (int i = 0; i < _identities.Count; i++)
            {
                if (_identities[i] != null)
                {
                    claim = _identities[i].FindFirst(type);
                    if (claim != null)
                    {
                        return claim;
                    }
                }
            }

            return claim;
        }

        /// <summary>
        /// Determines if a claim is contained within all the ClaimsIdentities in this ClaimPrincipal.
        /// </summary>
        /// <param name="match">The predicate that performs the matching logic.</param>
        /// <returns>true if a claim is found, false otherwise.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.HasClaim(string, string)"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'match' is null.</exception>
        public virtual bool HasClaim(Predicate<Claim> match)
        {
            if (match == null)
            {
                throw new ArgumentNullException(nameof(match));
            }

            for (int i = 0; i < _identities.Count; i++)
            {
                if (_identities[i] != null)
                {
                    if (_identities[i].HasClaim(match))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if a claim of claimType AND claimValue exists in any of the identities.
        /// </summary>
        /// <param name="type"> the type of the claim to match.</param>
        /// <param name="value"> the value of the claim to match.</param>
        /// <returns>true if a claim is matched, false otherwise.</returns>
        /// <remarks>Each <see cref="ClaimsIdentity"/> is called. <seealso cref="ClaimsIdentity.HasClaim(Predicate{Claim})"/>.</remarks>
        /// <exception cref="ArgumentNullException">if 'type' is null.</exception>
        /// <exception cref="ArgumentNullException">if 'value' is null.</exception>
        public virtual bool HasClaim(string type, string value)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

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

            for (int i = 0; i < _identities.Count; i++)
            {
                if (_identities[i] != null)
                {
                    if (_identities[i].HasClaim(type, value))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Collection of <see cref="ClaimsIdentity" />
        /// </summary>
        public virtual IEnumerable<ClaimsIdentity> Identities
        {
            get
            {
                return _identities;
            }
        }

        /// <summary>
        /// Gets the identity of the current principal.
        /// </summary>
        public virtual System.Security.Principal.IIdentity? Identity
        {
            get
            {
                if (s_identitySelector != null)
                {
                    return s_identitySelector(_identities);
                }
                else
                {
                    return SelectPrimaryIdentity(_identities);
                }
            }
        }

        /// <summary>
        /// IsInRole answers the question: does an identity this principal possesses
        /// contain a claim of type RoleClaimType where the value is '==' to the role.
        /// </summary>
        /// <param name="role">The role to check for.</param>
        /// <returns>'True' if a claim is found. Otherwise 'False'.</returns>
        /// <remarks>Each Identity has its own definition of the ClaimType that represents a role.</remarks>
        public virtual bool IsInRole(string role)
        {
            for (int i = 0; i < _identities.Count; i++)
            {
                if (_identities[i] != null)
                {
                    if (_identities[i].HasClaim(_identities[i].RoleClaimType, role))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        public virtual void WriteTo(BinaryWriter writer)
        {
            WriteTo(writer, null);
        }

        /// <summary>
        /// Serializes using a <see cref="BinaryWriter"/>
        /// </summary>
        /// <param name="writer">the <see cref="BinaryWriter"/> to use for data storage.</param>
        /// <param name="userData">additional data provided by derived type.</param>
        /// <exception cref="ArgumentNullException">if 'writer' is null.</exception>
        protected virtual void WriteTo(BinaryWriter writer, byte[]? userData)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            int numberOfPropertiesWritten = 0;
            var mask = SerializationMask.None;
            if (_identities.Count > 0)
            {
                mask |= SerializationMask.HasIdentities;
                numberOfPropertiesWritten++;
            }

            if (userData != null && userData.Length > 0)
            {
                numberOfPropertiesWritten++;
                mask |= SerializationMask.UserData;
            }

            writer.Write((int)mask);
            writer.Write(numberOfPropertiesWritten);
            if ((mask & SerializationMask.HasIdentities) == SerializationMask.HasIdentities)
            {
                writer.Write(_identities.Count);
                foreach (var identity in _identities)
                {
                    identity.WriteTo(writer);
                }
            }

            if ((mask & SerializationMask.UserData) == SerializationMask.UserData)
            {
                writer.Write(userData!.Length);
                writer.Write(userData);
            }

            writer.Flush();
        }

        [OnSerializing]
        private void OnSerializingMethod(StreamingContext context)
        {
            if (this is ISerializable)
            {
                return;
            }

            if (_identities.Count > 0)
            {
                throw new PlatformNotSupportedException(SR.PlatformNotSupported_Serialization); // BinaryFormatter and WindowsIdentity would be needed
            }
        }

        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }
    }

GenericPrincipal

public class GenericPrincipal : ClaimsPrincipal
{
    private readonly IIdentity m_identity;
    private readonly string[]? m_roles;

    public GenericPrincipal(IIdentity identity, string[]? roles)
    {
        if (identity == null)
            throw new ArgumentNullException(nameof(identity));

        m_identity = identity;
        if (roles != null)
        {
            m_roles = (string[])roles.Clone();
        }
        else
        {
            m_roles = null;
        }

        AddIdentityWithRoles(m_identity, m_roles);
    }

    /// <summary>
    /// helper method to add roles
    /// </summary>
    private void AddIdentityWithRoles(IIdentity identity, string[]? roles)
    {
        if (identity is ClaimsIdentity claimsIdentity)
        {
            claimsIdentity = claimsIdentity.Clone();
        }
        else
        {
            claimsIdentity = new ClaimsIdentity(identity);
        }

        // Add 'roles' as external claims so they are not serialized
        if (roles != null && roles.Length > 0)
        {
            List<Claim> roleClaims = new List<Claim>(roles.Length);

            foreach (string role in roles)
            {
                if (!string.IsNullOrWhiteSpace(role))
                {
                    roleClaims.Add(new Claim(claimsIdentity.RoleClaimType, role, ClaimValueTypes.String, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, claimsIdentity));
                }
            }

            claimsIdentity.ExternalClaims.Add(roleClaims);
        }

        base.AddIdentity(claimsIdentity);
    }

    public override IIdentity Identity
    {
        get { return m_identity; }
    }

    public override bool IsInRole(string? role)
    {
        if (role == null || m_roles == null)
            return false;

        for (int i = 0; i < m_roles.Length; ++i)
        {
            if (string.Equals(m_roles[i], role, StringComparison.OrdinalIgnoreCase))
                return true;
        }

        // it may be the case a ClaimsIdentity was passed in as the IIdentity which may have contained claims, they need to be checked.
        return base.IsInRole(role);
    }

    // This is called by AppDomain.GetThreadPrincipal() via reflection.
    private static IPrincipal GetDefaultInstance() => new GenericPrincipal(new GenericIdentity(string.Empty), new string[] { string.Empty });
}

AuthenticationTicket

AuthenticationTicket是对ClaimsPrincipal的封装

AuthenticationTicket

    /// <summary>
    /// Contains user identity information as well as additional authentication state.
    /// </summary>
    public class AuthenticationTicket
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
        /// </summary>
        /// <param name="principal">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
        /// <param name="properties">additional properties that can be consumed by the user or runtime.</param>
        /// <param name="authenticationScheme">the authentication scheme that was responsible for this ticket.</param>
        public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties? properties, string authenticationScheme)
        {
            if (principal == null)
            {
                throw new ArgumentNullException(nameof(principal));
            }

            AuthenticationScheme = authenticationScheme;
            Principal = principal;
            Properties = properties ?? new AuthenticationProperties();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
        /// </summary>
        /// <param name="principal">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
        /// <param name="authenticationScheme">the authentication scheme that was responsible for this ticket.</param>
        public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme) 
            : this(principal, properties: null, authenticationScheme: authenticationScheme)
        { }

        /// <summary>
        /// Gets the authentication scheme that was responsible for this ticket.
        /// </summary>
        public string AuthenticationScheme { get; }

        /// <summary>
        /// Gets the claims-principal with authenticated user identities.
        /// </summary>
        public ClaimsPrincipal Principal { get; }

        /// <summary>
        /// Additional state values for the authentication session.
        /// </summary>
        public AuthenticationProperties Properties { get; }

        /// <summary>
        /// Returns a copy of the ticket.
        /// </summary>
        /// <remarks>
        /// The method clones the <see cref="Principal"/> by calling <see cref="ClaimsIdentity.Clone"/> on each of the <see cref="ClaimsPrincipal.Identities"/>.
        /// </remarks>
        /// <returns>A copy of the ticket</returns>
        public AuthenticationTicket Clone()
        {
            var principal = new ClaimsPrincipal();
            foreach (var identity in Principal.Identities)
            {
                principal.AddIdentity(identity.Clone());
            }
            return new AuthenticationTicket(principal, Properties.Clone(), AuthenticationScheme);
        }
    }

AuthenticationProperties

包含很多与当提前认证上下文(Authentication Context)或者认证会话(Authentication Session)相关的信息,大部分属性都是对票据的描述。

    /// <summary>
    /// Dictionary used to store state values about the authentication session.
    /// </summary>
    public class AuthenticationProperties
    {
        internal const string IssuedUtcKey = ".issued";
        internal const string ExpiresUtcKey = ".expires";
        internal const string IsPersistentKey = ".persistent";
        internal const string RedirectUriKey = ".redirect";
        internal const string RefreshKey = ".refresh";
        internal const string UtcDateTimeFormat = "r";

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
        /// </summary>
        public AuthenticationProperties()
            : this(items: null, parameters: null)
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
        /// </summary>
        /// <param name="items">State values dictionary to use.</param>
        public AuthenticationProperties(IDictionary<string, string?> items)
            : this(items, parameters: null)
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
        /// </summary>
        /// <param name="items">State values dictionary to use.</param>
        /// <param name="parameters">Parameters dictionary to use.</param>
        public AuthenticationProperties(IDictionary<string, string?>? items, IDictionary<string, object?>? parameters)
        {
            Items = items ?? new Dictionary<string, string?>(StringComparer.Ordinal);
            Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal);
        }

        /// <summary>
        /// Return a copy.
        /// </summary>
        /// <returns>A copy.</returns>
        public AuthenticationProperties Clone()
            => new AuthenticationProperties(
                new Dictionary<string, string?>(Items, StringComparer.Ordinal),
                new Dictionary<string, object?>(Parameters, StringComparer.Ordinal));

        /// <summary>
        /// State values about the authentication session.
        /// </summary>
        public IDictionary<string, string?> Items { get; }

        /// <summary>
        /// Collection of parameters that are passed to the authentication handler. These are not intended for
        /// serialization or persistence, only for flowing data between call sites.
        /// </summary>
        public IDictionary<string, object?> Parameters { get; }

        /// <summary>
        /// Gets or sets whether the authentication session is persisted across multiple requests.
        /// </summary>
        public bool IsPersistent
        {
            get => GetString(IsPersistentKey) != null;
            set => SetString(IsPersistentKey, value ? string.Empty : null);
        }

        /// <summary>
        /// Gets or sets the full path or absolute URI to be used as an http redirect response value.
        /// </summary>
        public string? RedirectUri
        {
            get => GetString(RedirectUriKey);
            set => SetString(RedirectUriKey, value);
        }

        /// <summary>
        /// Gets or sets the time at which the authentication ticket was issued.
        /// </summary>
        public DateTimeOffset? IssuedUtc
        {
            get => GetDateTimeOffset(IssuedUtcKey);
            set => SetDateTimeOffset(IssuedUtcKey, value);
        }

        /// <summary>
        /// Gets or sets the time at which the authentication ticket expires.
        /// </summary>
        public DateTimeOffset? ExpiresUtc
        {
            get => GetDateTimeOffset(ExpiresUtcKey);
            set => SetDateTimeOffset(ExpiresUtcKey, value);
        }

        /// <summary>
        /// Gets or sets if refreshing the authentication session should be allowed.
        /// </summary>
        public bool? AllowRefresh
        {
            get => GetBool(RefreshKey);
            set => SetBool(RefreshKey, value);
        }

        /// <summary>
        /// Get a string value from the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
        public string? GetString(string key)
        {
            return Items.TryGetValue(key, out var value) ? value : null;
        }

        /// <summary>
        /// Set or remove a string value from the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
        public void SetString(string key, string? value)
        {
            if (value != null)
            {
                Items[key] = value;
            }
            else
            {
                Items.Remove(key);
            }
        }

        /// <summary>
        /// Get a parameter from the <see cref="Parameters"/> collection.
        /// </summary>
        /// <typeparam name="T">Parameter type.</typeparam>
        /// <param name="key">Parameter key.</param>
        /// <returns>Retrieved value or the default value if the property is not set.</returns>
        public T? GetParameter<T>(string key)
            => Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default;

        /// <summary>
        /// Set a parameter value in the <see cref="Parameters"/> collection.
        /// </summary>
        /// <typeparam name="T">Parameter type.</typeparam>
        /// <param name="key">Parameter key.</param>
        /// <param name="value">Value to set.</param>
        public void SetParameter<T>(string key, T value)
            => Parameters[key] = value;

        /// <summary>
        /// Get a nullable <see cref="bool"/> from the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
        protected bool? GetBool(string key)
        {
            if (Items.TryGetValue(key, out var value) && bool.TryParse(value, out var boolValue))
            {
                return boolValue;
            }
            return null;
        }

        /// <summary>
        /// Set or remove a <see cref="bool"/> value in the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
        protected void SetBool(string key, bool? value)
        {
            if (value.HasValue)
            {
                Items[key] = value.GetValueOrDefault().ToString();
            }
            else
            {
                Items.Remove(key);
            }
        }

        /// <summary>
        /// Get a nullable <see cref="DateTimeOffset"/> value from the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
        protected DateTimeOffset? GetDateTimeOffset(string key)
        {
            if (Items.TryGetValue(key, out var value)
                && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTimeOffset))
            {
                return dateTimeOffset;
            }
            return null;
        }

        /// <summary>
        /// Sets or removes a <see cref="DateTimeOffset" /> value in the <see cref="Items"/> collection.
        /// </summary>
        /// <param name="key">Property key.</param>
        /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
        protected void SetDateTimeOffset(string key, DateTimeOffset? value)
        {
            if (value.HasValue)
            {
                Items[key] = value.GetValueOrDefault().ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
            }
            else
            {
                Items.Remove(key);
            }
        }
    }

TicketDataFormat

对票据进行格式化处理

ISecureDataFormat<TData>

    /// <summary>
    /// A contract for securing data.
    /// </summary>
    /// <typeparam name="TData">The type of the data to protect.</typeparam>
    public interface ISecureDataFormat<TData>
    {
        /// <summary>
        /// Protects the specified <paramref name="data"/>.
        /// </summary>
        /// <param name="data">The value to protect</param>
        /// <returns>The data protected value.</returns>
        string Protect(TData data);

        /// <summary>
        /// Protects the specified <paramref name="data"/> for the specified <paramref name="purpose"/>.
        /// </summary>
        /// <param name="data">The value to protect</param>
        /// <param name="purpose">The purpose.</param>
        /// <returns>A data protected value.</returns>
        string Protect(TData data, string? purpose);

        /// <summary>
        /// Unprotects the specified <paramref name="protectedText"/>.
        /// </summary>
        /// <param name="protectedText">The data protected value.</param>
        /// <returns>An instance of <typeparamref name="TData"/>.</returns>
        [return: MaybeNull]
        TData Unprotect(string protectedText);

        /// <summary>
        /// Unprotects the specified <paramref name="protectedText"/> using the specified <paramref name="purpose"/>.
        /// </summary>
        /// <param name="protectedText">The data protected value.</param>
        /// <param name="purpose">The purpose.</param>
        /// <returns>An instance of <typeparamref name="TData"/>.</returns>
        [return: MaybeNull]
        TData Unprotect(string protectedText, string? purpose);
    }

SecureDataFormat<TData>

    /// <summary>
    /// An implementation for <see cref="ISecureDataFormat{TData}"/>.
    /// </summary>
    /// <typeparam name="TData"></typeparam>
    public class SecureDataFormat<TData> : ISecureDataFormat<TData>
    {
        private readonly IDataSerializer<TData> _serializer;
        private readonly IDataProtector _protector;

        /// <summary>
        /// Initializes a new instance of <see cref="SecureDataFormat{TData}"/>.
        /// </summary>
        /// <param name="serializer">The <see cref="IDataSerializer{TModel}"/>.</param>
        /// <param name="protector">The <see cref="IDataProtector"/>.</param>
        public SecureDataFormat(IDataSerializer<TData> serializer, IDataProtector protector)
        {
            _serializer = serializer;
            _protector = protector;
        }

        /// <inheritdoc />
        public string Protect(TData data)
        {
            return Protect(data, purpose: null);
        }

        /// <inheritdoc />
        public string Protect(TData data, string? purpose)
        {
            var userData = _serializer.Serialize(data);

            var protector = _protector;
            if (!string.IsNullOrEmpty(purpose))
            {
                protector = protector.CreateProtector(purpose);
            }

            var protectedData = protector.Protect(userData);
            return Base64UrlTextEncoder.Encode(protectedData);
        }

        /// <inheritdoc />
        [return: MaybeNull]
        public TData Unprotect(string protectedText)
        {
            return Unprotect(protectedText, purpose: null);
        }

        /// <inheritdoc />
        [return: MaybeNull]
        public TData Unprotect(string protectedText, string? purpose)
        {
            try
            {
                if (protectedText == null)
                {
                    return default(TData);
                }

                var protectedData = Base64UrlTextEncoder.Decode(protectedText);
                if (protectedData == null)
                {
                    return default(TData);
                }

                var protector = _protector;
                if (!string.IsNullOrEmpty(purpose))
                {
                    protector = protector.CreateProtector(purpose);
                }

                var userData = protector.Unprotect(protectedData);
                if (userData == null)
                {
                    return default(TData);
                }

                return _serializer.Deserialize(userData);
            }
            catch
            {
                // TODO trace exception, but do not leak other information
                return default(TData);
            }
        }
    }

TicketDataFormat

认证票据是一种私密性数据,需要进行加密和格式化,加密通过IDataProtector接口,格式化通过TiceketSerializer接口

要点TicketSerializer.Default

    /// <summary>
    /// A <see cref="SecureDataFormat{TData}"/> instance to secure
    /// <see cref="AuthenticationTicket"/>.
    /// </summary>
    public class TicketDataFormat : SecureDataFormat<AuthenticationTicket>
    {
        /// <summary>
        /// Initializes a new instance of <see cref="TicketDataFormat"/>.
        /// </summary>
        /// <param name="protector">The <see cref="IDataProtector"/>.</param>
        public TicketDataFormat(IDataProtector protector)
            : base(TicketSerializer.Default, protector)
        {
        }
    }

IDataProtectionProvider

  public interface IDataProtectionProvider
  {
    IDataProtector CreateProtector(string purpose);
  }

IDataProtector

  public interface IDataProtector : IDataProtectionProvider
  {
    byte[] Protect(byte[] plaintext);

    byte[] Unprotect(byte[] protectedData);
  }

IDataSerializer<TModel>

    /// <summary>
    /// Contract for serialzing authentication data.
    /// </summary>
    /// <typeparam name="TModel">The type of the model being serialized.</typeparam>
    public interface IDataSerializer<TModel>
    {
        /// <summary>
        /// Serializes the specified <paramref name="model"/>.
        /// </summary>
        /// <param name="model">The value to serialize.</param>
        /// <returns>The serialized data.</returns>
        byte[] Serialize(TModel model);

        /// <summary>
        /// Deserializes the specified <paramref name="data"/> as an instance of type <typeparamref name="TModel"/>.
        /// </summary>
        /// <param name="data">The bytes being deserialized.</param>
        /// <returns>The model.</returns>
        [return: MaybeNull]
        TModel Deserialize(byte[] data);
    }

TiceketSerializer

    // This MUST be kept in sync with Microsoft.Owin.Security.Interop.AspNetTicketSerializer
    /// <summary>
    /// Serializes and deserializes <see cref="AuthenticationTicket"/> instances.
    /// </summary>
    public class TicketSerializer : IDataSerializer<AuthenticationTicket>
    {
        private const string DefaultStringPlaceholder = "\0";
        private const int FormatVersion = 5;

        /// <summary>
        /// Gets the default implementation for <see cref="TicketSerializer"/>.
        /// </summary>
        public static TicketSerializer Default { get; } = new TicketSerializer();

        /// <inheritdoc/>
        public virtual byte[] Serialize(AuthenticationTicket ticket)
        {
            using (var memory = new MemoryStream())
            {
                using (var writer = new BinaryWriter(memory))
                {
                    Write(writer, ticket);
                }
                return memory.ToArray();
            }
        }

        /// <inheritdoc/>
        public virtual AuthenticationTicket? Deserialize(byte[] data)
        {
            using (var memory = new MemoryStream(data))
            {
                using (var reader = new BinaryReader(memory))
                {
                    return Read(reader);
                }
            }
        }

        /// <summary>
        /// Writes the <paramref name="ticket"/> using the specified <paramref name="writer"/>.
        /// </summary>
        /// <param name="writer">The <see cref="BinaryWriter"/>.</param>
        /// <param name="ticket">The <see cref="AuthenticationTicket"/>.</param>
        public virtual void Write(BinaryWriter writer, AuthenticationTicket ticket)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

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

            writer.Write(FormatVersion);
            writer.Write(ticket.AuthenticationScheme);

            // Write the number of identities contained in the principal.
            var principal = ticket.Principal;
            writer.Write(principal.Identities.Count());

            foreach (var identity in principal.Identities)
            {
                WriteIdentity(writer, identity);
            }

            PropertiesSerializer.Default.Write(writer, ticket.Properties);
        }

        /// <summary>
        /// Writes the specified <paramref name="identity" />.
        /// </summary>
        /// <param name="writer">The <see cref="BinaryWriter" />.</param>
        /// <param name="identity">The <see cref="ClaimsIdentity" />.</param>
        protected virtual void WriteIdentity(BinaryWriter writer, ClaimsIdentity identity)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

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

            var authenticationType = identity.AuthenticationType ?? string.Empty;

            writer.Write(authenticationType);
            WriteWithDefault(writer, identity.NameClaimType, ClaimsIdentity.DefaultNameClaimType);
            WriteWithDefault(writer, identity.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType);

            // Write the number of claims contained in the identity.
            writer.Write(identity.Claims.Count());

            foreach (var claim in identity.Claims)
            {
                WriteClaim(writer, claim);
            }

            var bootstrap = identity.BootstrapContext as string;
            if (!string.IsNullOrEmpty(bootstrap))
            {
                writer.Write(true);
                writer.Write(bootstrap);
            }
            else
            {
                writer.Write(false);
            }

            if (identity.Actor != null)
            {
                writer.Write(true);
                WriteIdentity(writer, identity.Actor);
            }
            else
            {
                writer.Write(false);
            }
        }

        /// <inheritdoc/>
        protected virtual void WriteClaim(BinaryWriter writer, Claim claim)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

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

            WriteWithDefault(writer, claim.Type, claim.Subject?.NameClaimType ?? ClaimsIdentity.DefaultNameClaimType);
            writer.Write(claim.Value);
            WriteWithDefault(writer, claim.ValueType, ClaimValueTypes.String);
            WriteWithDefault(writer, claim.Issuer, ClaimsIdentity.DefaultIssuer);
            WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer);

            // Write the number of properties contained in the claim.
            writer.Write(claim.Properties.Count);

            foreach (var property in claim.Properties)
            {
                writer.Write(property.Key ?? string.Empty);
                writer.Write(property.Value ?? string.Empty);
            }
        }

        /// <summary>
        /// Reads an <see cref="AuthenticationTicket"/>.
        /// </summary>
        /// <param name="reader">The <see cref="BinaryReader"/>.</param>
        /// <returns>The <see cref="AuthenticationTicket"/> if the format is supported, otherwise <see langword="null"/>.</returns>
        public virtual AuthenticationTicket? Read(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (reader.ReadInt32() != FormatVersion)
            {
                return null;
            }

            var scheme = reader.ReadString();

            // Read the number of identities stored
            // in the serialized payload.
            var count = reader.ReadInt32();
            if (count < 0)
            {
                return null;
            }

            var identities = new ClaimsIdentity[count];
            for (var index = 0; index != count; ++index)
            {
                identities[index] = ReadIdentity(reader);
            }

            var properties = PropertiesSerializer.Default.Read(reader);

            return new AuthenticationTicket(new ClaimsPrincipal(identities), properties, scheme);
        }

        /// <summary>
        /// Reads a <see cref="ClaimsIdentity"/> from a <see cref="BinaryReader"/>.
        /// </summary>
        /// <param name="reader">The <see cref="BinaryReader"/>.</param>
        /// <returns>The read <see cref="ClaimsIdentity"/>.</returns>
        protected virtual ClaimsIdentity ReadIdentity(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            var authenticationType = reader.ReadString();
            var nameClaimType = ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType);
            var roleClaimType = ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType);

            // Read the number of claims contained
            // in the serialized identity.
            var count = reader.ReadInt32();

            var identity = new ClaimsIdentity(authenticationType, nameClaimType, roleClaimType);

            for (int index = 0; index != count; ++index)
            {
                var claim = ReadClaim(reader, identity);

                identity.AddClaim(claim);
            }

            // Determine whether the identity
            // has a bootstrap context attached.
            if (reader.ReadBoolean())
            {
                identity.BootstrapContext = reader.ReadString();
            }

            // Determine whether the identity
            // has an actor identity attached.
            if (reader.ReadBoolean())
            {
                identity.Actor = ReadIdentity(reader);
            }

            return identity;
        }

        /// <summary>
        /// Reads a <see cref="Claim"/> and adds it to the specified <paramref name="identity"/>.
        /// </summary>
        /// <param name="reader">The <see cref="BinaryReader"/>.</param>
        /// <param name="identity">The <see cref="ClaimsIdentity"/> to add the claim to.</param>
        /// <returns>The read <see cref="Claim"/>.</returns>
        protected virtual Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

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

            var type = ReadWithDefault(reader, identity.NameClaimType);
            var value = reader.ReadString();
            var valueType = ReadWithDefault(reader, ClaimValueTypes.String);
            var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer);
            var originalIssuer = ReadWithDefault(reader, issuer);

            var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity);

            // Read the number of properties stored in the claim.
            var count = reader.ReadInt32();

            for (var index = 0; index != count; ++index)
            {
                var key = reader.ReadString();
                var propertyValue = reader.ReadString();

                claim.Properties.Add(key, propertyValue);
            }

            return claim;
        }

        private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
        {
            if (string.Equals(value, defaultValue, StringComparison.Ordinal))
            {
                writer.Write(DefaultStringPlaceholder);
            }
            else
            {
                writer.Write(value);
            }
        }

        private static string ReadWithDefault(BinaryReader reader, string defaultValue)
        {
            var value = reader.ReadString();
            if (string.Equals(value, DefaultStringPlaceholder, StringComparison.Ordinal))
            {
                return defaultValue;
            }
            return value;
        }
    }

认证模型

模型概览

image-20220406141614040

认证模型主要是依靠AuthorizationMiddleware中间件来实现的,

  • Scope创建AuthenticationHandlerProvider实例,

  • 通过AuthenticationSchemeProvider的GetRequestHandlerSchemesAsync方法获取AuthenticationScheme对象,根据AuthenticationScheme对象,从AuthenticationHandlerProvider中先获取IAuthenticationRequestHandler,

  • 调用AuthenticationSchemeProvider的GetDefaultAuthenticateSchemeAsync方法,再获取AuthenticationScheme对象,从通过context的AuthenticateAsync进行处理,实际是调用了AuthenticationService的AuthenticateAsync

  • 如果result的Principal对象存在就将其赋给context.User属性。认证完成。

认证模式:

  • 质询无效(401 Unauthorized):Task ChallengeAsync(AuthenticationProperties? properties);
  • 质询禁止(403 Forbidden):Task ForbidAsync(AuthenticationProperties? properties);
  • 认证(AuthenticateAsync):Task<AuthenticateResult> AuthenticateAsync();
  • 登录(SignIn):Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
  • 登出(SignOut): Task SignOutAsync(AuthenticationProperties properties);

AuthenticationSchemeProvider

AuthenticationScheme

认证方案名称,认证处理器的类型,

    /// <summary>
    /// AuthenticationSchemes assign a name to a specific <see cref="IAuthenticationHandler"/>
    /// handlerType.
    /// </summary>
    public class AuthenticationScheme
    {
        /// <summary>
        /// Initializes a new instance of <see cref="AuthenticationScheme"/>.
        /// </summary>
        /// <param name="name">The name for the authentication scheme.</param>
        /// <param name="displayName">The display name for the authentication scheme.</param>
        /// <param name="handlerType">The <see cref="IAuthenticationHandler"/> type that handles this scheme.</param>
        public AuthenticationScheme(string name, string? displayName, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (handlerType == null)
            {
                throw new ArgumentNullException(nameof(handlerType));
            }
            if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
            {
                throw new ArgumentException("handlerType must implement IAuthenticationHandler.");
            }

            Name = name;
            HandlerType = handlerType;
            DisplayName = displayName;
        }

        /// <summary>
        /// The name of the authentication scheme.
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// The display name for the scheme. Null is valid and used for non user facing schemes.
        /// </summary>
        public string? DisplayName { get; }

        /// <summary>
        /// The <see cref="IAuthenticationHandler"/> type that handles this scheme.
        /// </summary>
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
        public Type HandlerType { get; }
    }

AuthenticateOptions

存放着AuthenticationSchemeBuilder

    public class AuthenticationOptions
    {
        private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();

        /// <summary>
        /// Returns the schemes in the order they were added (important for request handling priority)
        /// </summary>
        public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;

        /// <summary>
        /// Maps schemes by name.
        /// </summary>
        public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = new Dictionary<string, AuthenticationSchemeBuilder>(StringComparer.Ordinal);

        /// <summary>
        /// Adds an <see cref="AuthenticationScheme"/>.
        /// </summary>
        /// <param name="name">The name of the scheme being added.</param>
        /// <param name="configureBuilder">Configures the scheme.</param>
        public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (configureBuilder == null)
            {
                throw new ArgumentNullException(nameof(configureBuilder));
            }
            if (SchemeMap.ContainsKey(name))
            {
                throw new InvalidOperationException("Scheme already exists: " + name);
            }

            var builder = new AuthenticationSchemeBuilder(name);
            configureBuilder(builder);
            _schemes.Add(builder);
            SchemeMap[name] = builder;
        }

        /// <summary>
        /// Adds an <see cref="AuthenticationScheme"/>.
        /// </summary>
        /// <typeparam name="THandler">The <see cref="IAuthenticationHandler"/> responsible for the scheme.</typeparam>
        /// <param name="name">The name of the scheme being added.</param>
        /// <param name="displayName">The display name for the scheme.</param>
        public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler
            => AddScheme(name, b =>
            {
                b.DisplayName = displayName;
                b.HandlerType = typeof(THandler);
            });

        /// <summary>
        /// Used as the fallback default scheme for all the other defaults.
        /// </summary>
        public string DefaultScheme { get; set; }

        /// <summary>
        /// Used as the default scheme by <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
        /// </summary>
        public string DefaultAuthenticateScheme { get; set; }

        /// <summary>
        /// Used as the default scheme by <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
        /// </summary>
        public string DefaultSignInScheme { get; set; }

        /// <summary>
        /// Used as the default scheme by <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// </summary>
        public string DefaultSignOutScheme { get; set; }

        /// <summary>
        /// Used as the default scheme by <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// </summary>
        public string DefaultChallengeScheme { get; set; }

        /// <summary>
        /// Used as the default scheme by <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// </summary>
        public string DefaultForbidScheme { get; set; }

        /// <summary>
        /// If true, SignIn should throw if attempted with a ClaimsPrincipal.Identity.IsAuthenticated = false.
        /// </summary>
        public bool RequireAuthenticatedSignIn { get; set; } = true;
    }

AuthenticationSchemeBuilder

  public class AuthenticationSchemeBuilder
  {
    public AuthenticationSchemeBuilder(string name) => this.Name = name;

    public string Name { get; }

    public string DisplayName { get; set; }

    public Type HandlerType { get; set; }

    public AuthenticationScheme Build() => new AuthenticationScheme(this.Name, this.DisplayName, this.HandlerType);
  }

IAuthenticationSchemeProvider

    /// <summary>
    /// Responsible for managing what authenticationSchemes are supported.
    /// </summary>
    public interface IAuthenticationSchemeProvider
    {
        /// <summary>
        /// Returns all currently registered <see cref="AuthenticationScheme"/>s.
        /// </summary>
        /// <returns>All currently registered <see cref="AuthenticationScheme"/>s.</returns>
        Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();

        /// <summary>
        /// Returns the <see cref="AuthenticationScheme"/> matching the name, or null.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme.</param>
        /// <returns>The scheme or null if not found.</returns>
        Task<AuthenticationScheme> GetSchemeAsync(string name);

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.</returns>
        Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
        /// Otherwise, this will fallback to <see cref="GetDefaultChallengeSchemeAsync"/> .
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme> GetDefaultForbidSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
        /// Otherwise, this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> .
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();

        /// <summary>
        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
        /// </summary>
        /// <param name="scheme">The scheme.</param>
        void AddScheme(AuthenticationScheme scheme);

        /// <summary>
        /// Removes a scheme, preventing it from being used by <see cref="IAuthenticationService"/>.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme being removed.</param>
        void RemoveScheme(string name);

        /// <summary>
        /// Returns the schemes in priority order for request handling.
        /// </summary>
        /// <returns>The schemes in priority order for request handling</returns>
        Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
    }

AuthenticationSchemeProvider

通过AuthenticateOptions中的AuthenticationSchemeBuilder来获取AuthenticationScheme

image-20220406164822046

    /// <summary>
    /// Implements <see cref="IAuthenticationSchemeProvider"/>.
    /// </summary>
    public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
    {
        /// <summary>
        /// Creates an instance of <see cref="AuthenticationSchemeProvider"/>
        /// using the specified <paramref name="options"/>,
        /// </summary>
        /// <param name="options">The <see cref="AuthenticationOptions"/> options.</param>
        public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
            : this(options, new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal))
        {
        }

        /// <summary>
        /// Creates an instance of <see cref="AuthenticationSchemeProvider"/>
        /// using the specified <paramref name="options"/> and <paramref name="schemes"/>.
        /// </summary>
        /// <param name="options">The <see cref="AuthenticationOptions"/> options.</param>
        /// <param name="schemes">The dictionary used to store authentication schemes.</param>
        protected AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes)
        {
            _options = options.Value;

            _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
            _requestHandlers = new List<AuthenticationScheme>();

            foreach (var builder in _options.Schemes)
            {
                var scheme = builder.Build();
                AddScheme(scheme);
            }
        }

        private readonly AuthenticationOptions _options;
        private readonly object _lock = new object();

        private readonly IDictionary<string, AuthenticationScheme> _schemes;
        private readonly List<AuthenticationScheme> _requestHandlers;
        // Used as a safe return value for enumeration apis
        private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
        private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();

        private Task<AuthenticationScheme> GetDefaultSchemeAsync()
            => _options.DefaultScheme != null
            ? GetSchemeAsync(_options.DefaultScheme)
            : Task.FromResult<AuthenticationScheme>(null);

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.</returns>
        public virtual Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
            => _options.DefaultAuthenticateScheme != null
            ? GetSchemeAsync(_options.DefaultAuthenticateScheme)
            : GetDefaultSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        public virtual Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
            => _options.DefaultChallengeScheme != null
            ? GetSchemeAsync(_options.DefaultChallengeScheme)
            : GetDefaultSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
        /// Otherwise, this will fallback to <see cref="GetDefaultChallengeSchemeAsync"/> .
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        public virtual Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
            => _options.DefaultForbidScheme != null
            ? GetSchemeAsync(_options.DefaultForbidScheme)
            : GetDefaultChallengeSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.</returns>
        public virtual Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
            => _options.DefaultSignInScheme != null
            ? GetSchemeAsync(_options.DefaultSignInScheme)
            : GetDefaultSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
        /// Otherwise this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> if that supports sign out.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        public virtual Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
            => _options.DefaultSignOutScheme != null
            ? GetSchemeAsync(_options.DefaultSignOutScheme)
            : GetDefaultSignInSchemeAsync();

        /// <summary>
        /// Returns the <see cref="AuthenticationScheme"/> matching the name, or null.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme.</param>
        /// <returns>The scheme or null if not found.</returns>
        public virtual Task<AuthenticationScheme> GetSchemeAsync(string name)
            => Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);

        /// <summary>
        /// Returns the schemes in priority order for request handling.
        /// </summary>
        /// <returns>The schemes in priority order for request handling</returns>
        public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
            => Task.FromResult(_requestHandlersCopy);

        /// <summary>
        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
        /// </summary>
        /// <param name="scheme">The scheme.</param>
        public virtual void AddScheme(AuthenticationScheme scheme)
        {
            if (_schemes.ContainsKey(scheme.Name))
            {
                throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
            }
            lock (_lock)
            {
                if (_schemes.ContainsKey(scheme.Name))
                {
                    throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
                }
                if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
                {
                    _requestHandlers.Add(scheme);
                    _requestHandlersCopy = _requestHandlers.ToArray();
                }
                _schemes[scheme.Name] = scheme;
                _schemesCopy = _schemes.Values.ToArray();
            }
        }

        /// <summary>
        /// Removes a scheme, preventing it from being used by <see cref="IAuthenticationService"/>.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme being removed.</param>
        public virtual void RemoveScheme(string name)
        {
            if (!_schemes.ContainsKey(name))
            {
                return;
            }
            lock (_lock)
            {
                if (_schemes.ContainsKey(name))
                {
                    var scheme = _schemes[name];
                    if (_requestHandlers.Remove(scheme))
                    {
                        _requestHandlersCopy = _requestHandlers.ToArray();
                    }
                    _schemes.Remove(name);
                    _schemesCopy = _schemes.Values.ToArray();
                }
            }
        }

        public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
            => Task.FromResult(_schemesCopy);
    }

AuthenticationHandlerProvider

提供Handler,对应5种认证方法

IAuthenticationHandlerProvider

    /// Responsible for managing what authenticationSchemes are supported.
    /// </summary>
    public interface IAuthenticationSchemeProvider
    {
        /// <summary>
        /// Returns all currently registered <see cref="AuthenticationScheme"/>s.
        /// </summary>
        /// <returns>All currently registered <see cref="AuthenticationScheme"/>s.</returns>
        Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();

        /// <summary>
        /// Returns the <see cref="AuthenticationScheme"/> matching the name, or null.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme.</param>
        /// <returns>The scheme or null if not found.</returns>
        Task<AuthenticationScheme?> GetSchemeAsync(string name);

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.</returns>
        Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
        /// Otherwise, this will fallback to <see cref="GetDefaultChallengeSchemeAsync"/> .
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync();

        /// <summary>
        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
        /// Otherwise, this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> .
        /// </summary>
        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
        Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync();

        /// <summary>
        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
        /// </summary>
        /// <param name="scheme">The scheme.</param>
        void AddScheme(AuthenticationScheme scheme);

        /// <summary>
        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
        /// </summary>
        /// <param name="scheme">The scheme.</param>
        /// <returns>true if the scheme was added successfully.</returns>
        bool TryAddScheme(AuthenticationScheme scheme)
        {
            try
            {
                AddScheme(scheme);
                return true;
            }
            catch {
                return false;
            }
        }

        /// <summary>
        /// Removes a scheme, preventing it from being used by <see cref="IAuthenticationService"/>.
        /// </summary>
        /// <param name="name">The name of the authenticationScheme being removed.</param>
        void RemoveScheme(string name);

        /// <summary>
        /// Returns the schemes in priority order for request handling.
        /// </summary>
        /// <returns>The schemes in priority order for request handling</returns>
        Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
    }

AuthenticationHandlerProvider

通过AuthenticationScheme反射创建IAuthenticationHandler对象,并进行缓存

    /// <summary>
    /// Implementation of <see cref="IAuthenticationHandlerProvider"/>.
    /// </summary>
    public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schemes">The <see cref="IAuthenticationHandlerProvider"/>.</param>
        public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
        {
            Schemes = schemes;
        }

        /// <summary>
        /// The <see cref="IAuthenticationHandlerProvider"/>.
        /// </summary>
        public IAuthenticationSchemeProvider Schemes { get; }

        // handler instance cache, need to initialize once per request
        private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);

        /// <summary>
        /// Returns the handler instance that will be used.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
        /// <returns>The handler instance.</returns>
        public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
        {
            if (_handlerMap.ContainsKey(authenticationScheme))
            {
                return _handlerMap[authenticationScheme];
            }

            var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
            if (scheme == null)
            {
                return null;
            }
            var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
                ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
                as IAuthenticationHandler;
            if (handler != null)
            {
                await handler.InitializeAsync(scheme, context);
                _handlerMap[authenticationScheme] = handler;
            }
            return handler;
        }
    }

IAuthenticationHandler

/// <summary>
/// Created per request to handle authentication for to a particular scheme.
/// </summary>
public interface IAuthenticationHandler
{
    /// <summary>
    /// The handler should initialize anything it needs from the request and scheme here.
    /// </summary>
    /// <param name="scheme">The <see cref="AuthenticationScheme"/> scheme.</param>
    /// <param name="context">The <see cref="HttpContext"/> context.</param>
    /// <returns></returns>
    Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);

    /// <summary>
    /// Authentication behavior.
    /// </summary>
    /// <returns>The <see cref="AuthenticateResult"/> result.</returns>
    Task<AuthenticateResult> AuthenticateAsync();

    /// <summary>
    /// Challenge behavior.
    /// </summary>
    /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
    /// <returns>A task.</returns>
    Task ChallengeAsync(AuthenticationProperties properties);

    /// <summary>
    /// Forbid behavior.
    /// </summary>
    /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
    /// <returns>A task.</returns>
    Task ForbidAsync(AuthenticationProperties properties);
}

AuthenticationHandler<TOptions>

image-20220406164748371

public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
{
    private Task<AuthenticateResult> _authenticateTask;

    public AuthenticationScheme Scheme { get; private set; }
    public TOptions Options { get; private set; }
    protected HttpContext Context { get; private set; }

    protected HttpRequest Request
    {
        get => Context.Request;
    }

    protected HttpResponse Response
    {
        get => Context.Response;
    }

    protected PathString OriginalPath => Context.Features.Get<IAuthenticationFeature>()?.OriginalPath ?? Request.Path;

    protected PathString OriginalPathBase => Context.Features.Get<IAuthenticationFeature>()?.OriginalPathBase ?? Request.PathBase;

    protected ILogger Logger { get; }

    protected UrlEncoder UrlEncoder { get; }

    protected ISystemClock Clock { get; }

    protected IOptionsMonitor<TOptions> OptionsMonitor { get; }

    /// <summary>
    /// The handler calls methods on the events which give the application control at certain points where processing is occurring. 
    /// If it is not provided a default instance is supplied which does nothing when the methods are called.
    /// </summary>
    protected virtual object Events { get; set; }

    protected virtual string ClaimsIssuer => Options.ClaimsIssuer ?? Scheme.Name;

    protected string CurrentUri
    {
        get => Request.Scheme + "://" + Request.Host + Request.PathBase + Request.Path + Request.QueryString;
    }

    protected AuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
    {
        Logger = logger.CreateLogger(this.GetType().FullName);
        UrlEncoder = encoder;
        Clock = clock;
        OptionsMonitor = options;
    }

    /// <summary>
    /// Initialize the handler, resolve the options and validate them.
    /// </summary>
    /// <param name="scheme"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
    {
        if (scheme == null)
        {
            throw new ArgumentNullException(nameof(scheme));
        }
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        Scheme = scheme;
        Context = context;

        Options = OptionsMonitor.Get(Scheme.Name);

        await InitializeEventsAsync();
        await InitializeHandlerAsync();
    }

    /// <summary>
    /// Initializes the events object, called once per request by <see cref="InitializeAsync(AuthenticationScheme, HttpContext)"/>.
    /// </summary>
    protected virtual async Task InitializeEventsAsync()
    {
        Events = Options.Events;
        if (Options.EventsType != null)
        {
            Events = Context.RequestServices.GetRequiredService(Options.EventsType);
        }
        Events = Events ?? await CreateEventsAsync();
    }

    /// <summary>
    /// Creates a new instance of the events instance.
    /// </summary>
    /// <returns>A new instance of the events instance.</returns>
    protected virtual Task<object> CreateEventsAsync() => Task.FromResult(new object());

    /// <summary>
    /// Called after options/events have been initialized for the handler to finish initializing itself.
    /// </summary>
    /// <returns>A task</returns>
    protected virtual Task InitializeHandlerAsync() => Task.CompletedTask;

    protected string BuildRedirectUri(string targetPath)
        => Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;

    protected virtual string ResolveTarget(string scheme)
    {
        var target = scheme ?? Options.ForwardDefaultSelector?.Invoke(Context) ?? Options.ForwardDefault;

        // Prevent self targetting
        return string.Equals(target, Scheme.Name, StringComparison.Ordinal)
            ? null
            : target;
    }

    public async Task<AuthenticateResult> AuthenticateAsync()
    {
        var target = ResolveTarget(Options.ForwardAuthenticate);
        if (target != null)
        {
            return await Context.AuthenticateAsync(target);
        }

        // Calling Authenticate more than once should always return the original value.
        var result = await HandleAuthenticateOnceAsync();
        if (result?.Failure == null)
        {
            var ticket = result?.Ticket;
            if (ticket?.Principal != null)
            {
                Logger.AuthenticationSchemeAuthenticated(Scheme.Name);
            }
            else
            {
                Logger.AuthenticationSchemeNotAuthenticated(Scheme.Name);
            }
        }
        else
        {
            Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Scheme.Name, result.Failure.Message);
        }
        return result;
    }

    /// <summary>
    /// Used to ensure HandleAuthenticateAsync is only invoked once. The subsequent calls
    /// will return the same authenticate result.
    /// </summary>
    protected Task<AuthenticateResult> HandleAuthenticateOnceAsync()
    {
        if (_authenticateTask == null)
        {
            _authenticateTask = HandleAuthenticateAsync();
        }

        return _authenticateTask;
    }

    /// <summary>
    /// Used to ensure HandleAuthenticateAsync is only invoked once safely. The subsequent
    /// calls will return the same authentication result. Any exceptions will be converted
    /// into a failed authentication result containing the exception.
    /// </summary>
    protected async Task<AuthenticateResult> HandleAuthenticateOnceSafeAsync()
    {
        try
        {
            return await HandleAuthenticateOnceAsync();
        }
        catch (Exception ex)
        {
            return AuthenticateResult.Fail(ex);
        }
    }

    protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();

    /// <summary>
    /// Override this method to handle Forbid.
    /// </summary>
    /// <param name="properties"></param>
    /// <returns>A Task.</returns>
    protected virtual Task HandleForbiddenAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = 403;
        return Task.CompletedTask;
    }

    /// <summary>
    /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question
    /// deals an authentication interaction as part of it's request flow. (like adding a response header, or
    /// changing the 401 result to 302 of a login page or external sign-in location.)
    /// </summary>
    /// <param name="properties"></param>
    /// <returns>A Task.</returns>
    protected virtual Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = 401;
        return Task.CompletedTask;
    }

    public async Task ChallengeAsync(AuthenticationProperties properties)
    {
        var target = ResolveTarget(Options.ForwardChallenge);
        if (target != null)
        {
            await Context.ChallengeAsync(target, properties);
            return;
        }

        properties = properties ?? new AuthenticationProperties();
        await HandleChallengeAsync(properties);
        Logger.AuthenticationSchemeChallenged(Scheme.Name);
    }

    public async Task ForbidAsync(AuthenticationProperties properties)
    {
        var target = ResolveTarget(Options.ForwardForbid);
        if (target != null)
        {
            await Context.ForbidAsync(target, properties);
            return;
        }

        properties = properties ?? new AuthenticationProperties();
        await HandleForbiddenAsync(properties);
        Logger.AuthenticationSchemeForbidden(Scheme.Name);
    }
}

AuthenticationSchemeOptions

认证处理器的配置

image-20220406165021378

    /// <summary>
    /// Contains the options used by the <see cref="AuthenticationHandler{T}"/>.
    /// </summary>
    public class AuthenticationSchemeOptions
    {
        /// <summary>
        /// Check that the options are valid. Should throw an exception if things are not ok.
        /// </summary>
        public virtual void Validate() { }

        /// <summary>
        /// Checks that the options are valid for a specific scheme
        /// </summary>
        /// <param name="scheme">The scheme being validated.</param>
        public virtual void Validate(string scheme)
            => Validate();

        /// <summary>
        /// Gets or sets the issuer that should be used for any claims that are created
        /// </summary>
        public string ClaimsIssuer { get; set; }

        /// <summary>
        /// Instance used for events
        /// </summary>
        public object Events { get; set; }

        /// <summary>
        /// If set, will be used as the service type to get the Events instance instead of the property.
        /// </summary>
        public Type EventsType { get; set; }

        /// <summary>
        /// If set, this specifies a default scheme that authentication handlers should forward all authentication operations to
        /// by default. The default forwarding logic will check the most specific ForwardAuthenticate/Challenge/Forbid/SignIn/SignOut
        /// setting first, followed by checking the ForwardDefaultSelector, followed by ForwardDefault. The first non null result
        /// will be used as the target scheme to forward to.
        /// </summary>
        public string ForwardDefault { get; set; }

        /// <summary>
        /// If set, this specifies the target scheme that this scheme should forward AuthenticateAsync calls to.
        /// For example Context.AuthenticateAsync("ThisScheme") => Context.AuthenticateAsync("ForwardAuthenticateValue");
        /// Set the target to the current scheme to disable forwarding and allow normal processing.
        /// </summary>
        public string ForwardAuthenticate { get; set; }

        /// <summary>
        /// If set, this specifies the target scheme that this scheme should forward ChallengeAsync calls to.
        /// For example Context.ChallengeAsync("ThisScheme") => Context.ChallengeAsync("ForwardChallengeValue");
        /// Set the target to the current scheme to disable forwarding and allow normal processing.
        /// </summary>
        public string ForwardChallenge { get; set; }

        /// <summary>
        /// If set, this specifies the target scheme that this scheme should forward ForbidAsync calls to.
        /// For example Context.ForbidAsync("ThisScheme") => Context.ForbidAsync("ForwardForbidValue");
        /// Set the target to the current scheme to disable forwarding and allow normal processing.
        /// </summary>
        public string ForwardForbid { get; set; }

        /// <summary>
        /// If set, this specifies the target scheme that this scheme should forward SignInAsync calls to.
        /// For example Context.SignInAsync("ThisScheme") => Context.SignInAsync("ForwardSignInValue");
        /// Set the target to the current scheme to disable forwarding and allow normal processing.
        /// </summary>
        public string ForwardSignIn { get; set; }

        /// <summary>
        /// If set, this specifies the target scheme that this scheme should forward SignOutAsync calls to.
        /// For example Context.SignOutAsync("ThisScheme") => Context.SignOutAsync("ForwardSignOutValue");
        /// Set the target to the current scheme to disable forwarding and allow normal processing.
        /// </summary>
        public string ForwardSignOut { get; set; }

        /// <summary>
        /// Used to select a default scheme for the current request that authentication handlers should forward all authentication operations to
        /// by default. The default forwarding logic will check the most specific ForwardAuthenticate/Challenge/Forbid/SignIn/SignOut
        /// setting first, followed by checking the ForwardDefaultSelector, followed by ForwardDefault. The first non null result
        /// will be used as the target scheme to forward to.
        /// </summary>
        public Func<HttpContext, string> ForwardDefaultSelector { get; set; }

    }

SignOutAuthenticationHandler<TOptions>

    /// <summary>
    /// Used to determine if a handler supports SignOut.
    /// </summary>
    public interface IAuthenticationSignOutHandler : IAuthenticationHandler
    {
        /// <summary>
        /// Signout behavior.
        /// </summary>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
        /// <returns>A task.</returns>
        Task SignOutAsync(AuthenticationProperties properties);
    }
    /// <summary>
    /// Adds support for SignOutAsync
    /// </summary>
    public abstract class SignOutAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationSignOutHandler
        where TOptions : AuthenticationSchemeOptions, new()
    {
        public SignOutAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
        { }

        public virtual Task SignOutAsync(AuthenticationProperties properties)
        {
            var target = ResolveTarget(Options.ForwardSignOut);
            return (target != null)
                ? Context.SignOutAsync(target, properties)
                : HandleSignOutAsync(properties ?? new AuthenticationProperties());
        }

        /// <summary>
        /// Override this method to handle SignOut.
        /// </summary>
        /// <param name="properties"></param>
        /// <returns>A Task.</returns>
        protected abstract Task HandleSignOutAsync(AuthenticationProperties properties);
    }

SignInAuthenticationHandler<TOptions>

    /// <summary>
    /// Used to determine if a handler supports SignIn.
    /// </summary>
    public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
    {
        /// <summary>
        /// Handle sign in.
        /// </summary>
        /// <param name="user">The <see cref="ClaimsPrincipal"/> user.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
        /// <returns>A task.</returns>
        Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
    }
    /// <summary>
    /// Adds support for SignInAsync
    /// </summary>
    public abstract class SignInAuthenticationHandler<TOptions> : SignOutAuthenticationHandler<TOptions>, IAuthenticationSignInHandler
        where TOptions : AuthenticationSchemeOptions, new()
    {
        public SignInAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
        { }

        public virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
        {
            var target = ResolveTarget(Options.ForwardSignIn);
            return (target != null)
                ? Context.SignInAsync(target, user, properties)
                : HandleSignInAsync(user, properties ?? new AuthenticationProperties());
        }

        /// <summary>
        /// Override this method to handle SignIn.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="properties"></param>
        /// <returns>A Task.</returns>
        protected abstract Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);

    }

AuthenticationService

认证服务

image-20220406165117138

    /// <summary>
    /// Implements <see cref="IAuthenticationService"/>.
    /// </summary>
    public class AuthenticationService : IAuthenticationService
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
        /// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
        /// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
        /// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
        public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options)
        {
            Schemes = schemes;
            Handlers = handlers;
            Transform = transform;
            Options = options.Value;
        }

        /// <summary>
        /// Used to lookup AuthenticationSchemes.
        /// </summary>
        public IAuthenticationSchemeProvider Schemes { get; }

        /// <summary>
        /// Used to resolve IAuthenticationHandler instances.
        /// </summary>
        public IAuthenticationHandlerProvider Handlers { get; }

        /// <summary>
        /// Used for claims transformation.
        /// </summary>
        public IClaimsTransformation Transform { get; }

        /// <summary>
        /// The <see cref="AuthenticationOptions"/>.
        /// </summary>
        public AuthenticationOptions Options { get; }

        /// <summary>
        /// Authenticate for the specified authentication scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <returns>The result.</returns>
        public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
        {
            if (scheme == null)
            {
                var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
                scheme = defaultScheme?.Name;
                if (scheme == null)
                {
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
                }
            }

            var handler = await Handlers.GetHandlerAsync(context, scheme);
            if (handler == null)
            {
                throw await CreateMissingHandlerException(scheme);
            }

            var result = await handler.AuthenticateAsync();
            if (result != null && result.Succeeded)
            {
                var transformed = await Transform.TransformAsync(result.Principal);
                return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
            }
            return result;
        }

        /// <summary>
        /// Challenge the specified authentication scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
        /// <returns>A task.</returns>
        public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
        {
            if (scheme == null)
            {
                var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
                scheme = defaultChallengeScheme?.Name;
                if (scheme == null)
                {
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
                }
            }

            var handler = await Handlers.GetHandlerAsync(context, scheme);
            if (handler == null)
            {
                throw await CreateMissingHandlerException(scheme);
            }

            await handler.ChallengeAsync(properties);
        }

        /// <summary>
        /// Forbid the specified authentication scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
        /// <returns>A task.</returns>
        public virtual async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
        {
            if (scheme == null)
            {
                var defaultForbidScheme = await Schemes.GetDefaultForbidSchemeAsync();
                scheme = defaultForbidScheme?.Name;
                if (scheme == null)
                {
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
                }
            }

            var handler = await Handlers.GetHandlerAsync(context, scheme);
            if (handler == null)
            {
                throw await CreateMissingHandlerException(scheme);
            }

            await handler.ForbidAsync(properties);
        }

        /// <summary>
        /// Sign a principal in for the specified authentication scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="principal">The <see cref="ClaimsPrincipal"/> to sign in.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
        /// <returns>A task.</returns>
        public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
        {
            if (principal == null)
            {
                throw new ArgumentNullException(nameof(principal));
            }

            if (Options.RequireAuthenticatedSignIn)
            {
                if (principal.Identity == null)
                {
                    throw new InvalidOperationException("SignInAsync when principal.Identity == null is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
                }
                if (!principal.Identity.IsAuthenticated)
                {
                    throw new InvalidOperationException("SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
                }
            }

            if (scheme == null)
            {
                var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
                scheme = defaultScheme?.Name;
                if (scheme == null)
                {
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
                }
            }

            var handler = await Handlers.GetHandlerAsync(context, scheme);
            if (handler == null)
            {
                throw await CreateMissingSignInHandlerException(scheme);
            }

            var signInHandler = handler as IAuthenticationSignInHandler;
            if (signInHandler == null)
            {
                throw await CreateMismatchedSignInHandlerException(scheme, handler);
            }

            await signInHandler.SignInAsync(principal, properties);
        }

        /// <summary>
        /// Sign out the specified authentication scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
        /// <returns>A task.</returns>
        public virtual async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
        {
            if (scheme == null)
            {
                var defaultScheme = await Schemes.GetDefaultSignOutSchemeAsync();
                scheme = defaultScheme?.Name;
                if (scheme == null)
                {
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
                }
            }

            var handler = await Handlers.GetHandlerAsync(context, scheme);
            if (handler == null)
            {
                throw await CreateMissingSignOutHandlerException(scheme);
            }

            var signOutHandler = handler as IAuthenticationSignOutHandler;
            if (signOutHandler == null)
            {
                throw await CreateMismatchedSignOutHandlerException(scheme, handler);
            }

            await signOutHandler.SignOutAsync(properties);
        }

        private async Task<Exception> CreateMissingHandlerException(string scheme)
        {
            var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));

            var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";

            if (string.IsNullOrEmpty(schemes))
            {
                return new InvalidOperationException(
                    $"No authentication handlers are registered." + footer);
            }

            return new InvalidOperationException(
                $"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
        }

        private async Task<string> GetAllSignInSchemeNames()
        {
            return string.Join(", ", (await Schemes.GetAllSchemesAsync())
                .Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
                .Select(sch => sch.Name));
        }

        private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
        {
            var schemes = await GetAllSignInSchemeNames();

            // CookieAuth is the only implementation of sign-in.
            var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";

            if (string.IsNullOrEmpty(schemes))
            {
                return new InvalidOperationException(
                    $"No sign-in authentication handlers are registered." + footer);
            }

            return new InvalidOperationException(
                $"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
        }

        private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
        {
            var schemes = await GetAllSignInSchemeNames();

            var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";

            if (string.IsNullOrEmpty(schemes))
            {
                // CookieAuth is the only implementation of sign-in.
                return new InvalidOperationException(mismatchError
                    + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
            }

            return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");
        }

        private async Task<string> GetAllSignOutSchemeNames()
        {
            return string.Join(", ", (await Schemes.GetAllSchemesAsync())
                .Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
                .Select(sch => sch.Name));
        }

        private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
        {
            var schemes = await GetAllSignOutSchemeNames();

            var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";

            if (string.IsNullOrEmpty(schemes))
            {
                // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
                return new InvalidOperationException($"No sign-out authentication handlers are registered." + footer);
            }

            return new InvalidOperationException(
                $"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
        }

        private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
        {
            var schemes = await GetAllSignOutSchemeNames();

            var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";

            if (string.IsNullOrEmpty(schemes))
            {
                // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
                return new InvalidOperationException(mismatchError
                    + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
            }

            return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
        }
    }

AuthenticationMiddleware

认证核心逻辑

context.AuthenticateAsync实际是调用的AuthenticationService

    /// <summary>
    /// Middleware that performs authentication.
    /// </summary>
    public class AuthenticationMiddleware
    {
        private readonly RequestDelegate _next;

        /// <summary>
        /// Initializes a new instance of <see cref="AuthenticationMiddleware"/>.
        /// </summary>
        /// <param name="next">The next item in the middleware pipeline.</param>
        /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
        public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }
            if (schemes == null)
            {
                throw new ArgumentNullException(nameof(schemes));
            }

            _next = next;
            Schemes = schemes;
        }

        /// <summary>
        /// Gets or sets the <see cref="IAuthenticationSchemeProvider"/>.
        /// </summary>
        public IAuthenticationSchemeProvider Schemes { get; set; }

        /// <summary>
        /// Invokes the middleware performing authentication.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/>.</param>
        public async Task Invoke(HttpContext context)
        {
            context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = context.Request.Path,
                OriginalPathBase = context.Request.PathBase
            });

            // Give any IAuthenticationRequestHandler schemes a chance to handle the request
            var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
                if (handler != null && await handler.HandleRequestAsync())
                {
                    return;
                }
            }

            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
                if (result?.Principal != null)
                {
                    context.User = result.Principal;
                }
            }

            await _next(context);
        }
    }

AuthenticationHttpContextExtensions

    /// <summary>
    /// Extension methods to expose Authentication on HttpContext.
    /// </summary>
    public static class AuthenticationHttpContextExtensions
    {
        /// <summary>
        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/> scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <returns>The <see cref="AuthenticateResult"/>.</returns>
        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) =>
            context.AuthenticateAsync(scheme: null);

        /// <summary>
        /// Extension method for authenticate.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <returns>The <see cref="AuthenticateResult"/>.</returns>
        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);

        /// <summary>
        /// Extension method for Challenge.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <returns>The result.</returns>
        public static Task ChallengeAsync(this HttpContext context, string scheme) =>
            context.ChallengeAsync(scheme, properties: null);

        /// <summary>
        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultChallengeScheme"/> scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <returns>The task.</returns>
        public static Task ChallengeAsync(this HttpContext context) =>
            context.ChallengeAsync(scheme: null, properties: null);

        /// <summary>
        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultChallengeScheme"/> scheme.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties) =>
            context.ChallengeAsync(scheme: null, properties: properties);

        /// <summary>
        /// Extension method for Challenge.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().ChallengeAsync(context, scheme, properties);

        /// <summary>
        /// Extension method for Forbid.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <returns>The task.</returns>
        public static Task ForbidAsync(this HttpContext context, string scheme) =>
            context.ForbidAsync(scheme, properties: null);

        /// <summary>
        /// Extension method for Forbid using the <see cref="AuthenticationOptions.DefaultForbidScheme"/> scheme..
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <returns>The task.</returns>
        public static Task ForbidAsync(this HttpContext context) =>
            context.ForbidAsync(scheme: null, properties: null);

        /// <summary>
        /// Extension method for Forbid.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties) =>
            context.ForbidAsync(scheme: null, properties: properties);

        /// <summary>
        /// Extension method for Forbid.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().ForbidAsync(context, scheme, properties);

        /// <summary>
        /// Extension method for SignIn.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="principal">The user.</param>
        /// <returns>The task.</returns>
        public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) =>
            context.SignInAsync(scheme, principal, properties: null);

        /// <summary>
        /// Extension method for SignIn using the <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="principal">The user.</param>
        /// <returns>The task.</returns>
        public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) =>
            context.SignInAsync(scheme: null, principal: principal, properties: null);

        /// <summary>
        /// Extension method for SignIn using the <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="principal">The user.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties) =>
            context.SignInAsync(scheme: null, principal: principal, properties: properties);

        /// <summary>
        /// Extension method for SignIn.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="principal">The user.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties);

        /// <summary>
        /// Extension method for SignOut using the <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <returns>The task.</returns>
        public static Task SignOutAsync(this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);

        /// <summary>
        /// Extension method for SignOut using the <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties) => context.SignOutAsync(scheme: null, properties: properties);

        /// <summary>
        /// Extension method for SignOut.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <returns>The task.</returns>
        public static Task SignOutAsync(this HttpContext context, string scheme) => context.SignOutAsync(scheme, properties: null);

        /// <summary>
        /// Extension method for SignOut.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
        /// <returns>The task.</returns>
        public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().SignOutAsync(context, scheme, properties);

        /// <summary>
        /// Extension method for getting the value of an authentication token.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="scheme">The name of the authentication scheme.</param>
        /// <param name="tokenName">The name of the token.</param>
        /// <returns>The value of the token.</returns>
        public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, scheme, tokenName);

        /// <summary>
        /// Extension method for getting the value of an authentication token.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> context.</param>
        /// <param name="tokenName">The name of the token.</param>
        /// <returns>The value of the token.</returns>
        public static Task<string> GetTokenAsync(this HttpContext context, string tokenName) =>
            context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, tokenName);

依赖注入

AuthenticationCoreServiceCollectionExtensions

    /// <summary>
    /// Extension methods for setting up authentication services in an <see cref="IServiceCollection" />.
    /// </summary>
    public static class AuthenticationCoreServiceCollectionExtensions
    {
        /// <summary>
        /// Add core authentication services needed for <see cref="IAuthenticationService"/>.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
        /// <returns>The service collection.</returns>
        public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services.TryAddScoped<IAuthenticationService, AuthenticationService>();
            services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
            services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
            services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
            return services;
        }

        /// <summary>
        /// Add core authentication services needed for <see cref="IAuthenticationService"/>.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
        /// <param name="configureOptions">Used to configure the <see cref="AuthenticationOptions"/>.</param>
        /// <returns>The service collection.</returns>
        public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

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

            services.AddAuthenticationCore();
            services.Configure(configureOptions);
            return services;
        }
    }

AuthAppBuilderExtensions

    /// <summary>
    /// Extension methods to add authentication capabilities to an HTTP application pipeline.
    /// </summary>
    public static class AuthAppBuilderExtensions
    {
        /// <summary>
        /// Adds the <see cref="AuthenticationMiddleware"/> to the specified <see cref="IApplicationBuilder"/>, which enables authentication capabilities.
        /// </summary>
        /// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
        /// <returns>A reference to this instance after the operation has completed.</returns>
        public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }
            
            return app.UseMiddleware<AuthenticationMiddleware>();
        }
    }
posted @ 2022-04-06 18:12  阿杜888  阅读(195)  评论(0编辑  收藏  举报