AspNetCore 认证 <1.0>
概览
认证(Authentication):用来给用户颁发一个凭证,其包含一个用户的基本信息,这个凭证可以由第三方机构(STS)颁发,也可以由自己颁发。
认证就涉及到两个模型:票据模型和认证模型。
票据模型抽象出用户凭证
认证模型通过中间件的方式给用户颁发凭证。
票据模型
模型概览
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:表示身份委托
/// <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; }
/// <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;
}
}
认证模型
模型概览
认证模型主要是依靠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
/// <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>
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
认证处理器的配置
/// <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
认证服务
/// <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>();
}
}
您的资助是我最大的动力!
金额随意,欢迎来赏!
出处:https://www.cnblogs.com/lovexinyi/
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文链接;否则必究法律责任