AspNetCore 认证 <1.0>
概览
认证(Authentication):用来给用户颁发一个凭证,其包含一个用户的基本信息,这个凭证可以由第三方机构(STS)颁发,也可以由自己颁发。
认证就涉及到两个模型:票据模型和认证模型。
票据模型抽象出用户凭证
认证模型通过中间件的方式给用户颁发凭证。
票据模型
模型概览
Claim:声明
ClaimsIdentity:身份,包含多个声明
ClaimPrincipal:用户,包含多个身份
AuthenticationTicket:票据,封装用户
TicketDataFormat:对票据进行处理,通过IDataProtector:对票据进行加密解密,通过TicketSerializer对票据进行序列化和反序列化。
Claim
陈述/声明
身份确认之后,认证方赋予的陈述,可以携带任何与认证用户相关的信息,可序列化,在网络中传递。
要点:
- Type属性:陈述的类型
- Value属性:陈述的值
- ValueType属性:陈述值的类型
- Issuer属性:颁发者
- OriginalIssure属性:原始颁发者
- Subject属性:陈述主题的ClaimsIdnetity对象
///
/// 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.
///
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? _properties;
private readonly ClaimsIdentity? _subject;
private readonly string _type;
private readonly string _value;
private readonly string _valueType;
///
/// Initializes an instance of using a .
/// Normally the is constructed using the bytes from and initialized in the same way as the .
///
/// a pointing to a .
/// if 'reader' is null.
public Claim(BinaryReader reader)
: this(reader, null)
{
}
///
/// Initializes an instance of using a .
/// Normally the is constructed using the bytes from and initialized in the same way as the .
///
/// a pointing to a .
/// the value for , which is the that has these claims.
/// if 'reader' is null.
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();
}
}
///
/// Creates a with the specified type and value.
///
/// The claim type.
/// The claim value.
/// or is null.
///
/// is set to ,
/// is set to ,
/// is set to , and
/// is set to null.
///
///
///
///
public Claim(string type, string value)
: this(type, value, ClaimValueTypes.String, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, (ClaimsIdentity?)null)
{
}
///
/// Creates a with the specified type, value, and value type.
///
/// The claim type.
/// The claim value.
/// The claim value type.
/// or is null.
///
/// is set to ,
/// is set to ,
/// and is set to null.
///
///
///
///
public Claim(string type, string value, string? valueType)
: this(type, value, valueType, ClaimsIdentity.DefaultIssuer, ClaimsIdentity.DefaultIssuer, (ClaimsIdentity?)null)
{
}
///
/// Creates a with the specified type, value, value type, and issuer.
///
/// The claim type.
/// The claim value.
/// The claim value type. If this parameter is empty or null, then is used.
/// The claim issuer. If this parameter is empty or null, then is used.
/// or is null.
///
/// is set to value of the parameter,
/// is set to null.
///
///
///
///
public Claim(string type, string value, string? valueType, string? issuer)
: this(type, value, valueType, issuer, issuer, (ClaimsIdentity?)null)
{
}
///
/// Creates a with the specified type, value, value type, issuer and original issuer.
///
/// The claim type.
/// The claim value.
/// The claim value type. If this parameter is null, then is used.
/// The claim issuer. If this parameter is empty or null, then is used.
/// The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.
/// or is null.
///
/// is set to null.
///
///
///
///
public Claim(string type, string value, string? valueType, string? issuer, string? originalIssuer)
: this(type, value, valueType, issuer, originalIssuer, (ClaimsIdentity?)null)
{
}
///
/// Creates a with the specified type, value, value type, issuer, original issuer and subject.
///
/// The claim type.
/// The claim value.
/// The claim value type. If this parameter is null, then is used.
/// The claim issuer. If this parameter is empty or null, then is used.
/// The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.
/// The subject that this claim describes.
/// or is null.
///
///
///
public Claim(string type, string value, string? valueType, string? issuer, string? originalIssuer, ClaimsIdentity? subject)
: this(type, value, valueType, issuer, originalIssuer, subject, null, null)
{
}
///
/// 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.
///
/// The claim type.
/// The claim value.
/// The claim value type. If this parameter is null, then is used.
/// The claim issuer. If this parameter is empty or null, then is used.
/// The original issuer of this claim. If this parameter is empty or null, then originalIssuer == issuer.
/// The subject that this claim describes.
/// This allows adding a property when adding a Claim.
/// The value associated with the property.
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();
_properties[propertyKey] = propertyValue!;
}
}
///
/// Copy constructor for
///
/// the to copy.
/// will be set to 'null'.
/// if 'other' is null.
protected Claim(Claim other)
: this(other, (other == null ? (ClaimsIdentity?)null : other._subject))
{
}
///
/// Copy constructor for
///
/// the to copy.
/// the to assign to .
/// will be set to 'subject'.
/// if 'other' is null.
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(other._properties);
}
if (other._userSerializationData != null)
{
_userSerializationData = other._userSerializationData.Clone() as byte[];
}
}
///
/// Contains any additional data provided by a derived type, typically set when calling .
///
protected virtual byte[]? CustomSerializationData
{
get
{
return _userSerializationData;
}
}
///
/// Gets the issuer of the .
///
public string Issuer
{
get { return _issuer; }
}
///
/// Gets the original issuer of the .
///
///
/// When the differs from the , it means
/// that the claim was issued by the and was re-issued
/// by the .
///
public string OriginalIssuer
{
get { return _originalIssuer; }
}
///
/// Gets the collection of Properties associated with the .
///
public IDictionary Properties
{
get
{
if (_properties == null)
{
_properties = new Dictionary();
}
return _properties;
}
}
///
/// Gets the subject of the .
///
public ClaimsIdentity? Subject
{
get { return _subject; }
}
///
/// Gets the claim type of the .
/// .
///
public string Type
{
get { return _type; }
}
///
/// Gets the value of the .
///
public string Value
{
get { return _value; }
}
///
/// Gets the value type of the .
///
///
public string ValueType
{
get { return _valueType; }
}
///
/// Creates a new instance with values copied from this object.
///
public virtual Claim Clone()
{
return Clone((ClaimsIdentity?)null);
}
///
/// Creates a new instance with values copied from this object.
///
/// the value for , which is the that has these claims.
/// will be set to 'identity'.
public virtual Claim Clone(ClaimsIdentity? identity)
{
return new Claim(this, identity);
}
///
/// Serializes using a
///
/// the to use for data storage.
/// if 'writer' is null.
public virtual void WriteTo(BinaryWriter writer)
{
WriteTo(writer, null);
}
///
/// Serializes using a
///
/// the to use for data storage.
/// additional data provided by derived type.
/// if 'writer' is null.
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();
}
///
/// Returns a string representation of the object.
///
///
/// The returned string contains the values of the and properties.
///
/// The string representation of the object.
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:表示身份委托
///
/// An Identity that is represented by a set of claims.
///
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>? _externalClaims;
private string? _label;
private readonly List _instanceClaims = new List();
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.
///
/// Initializes an instance of .
///
public ClaimsIdentity()
: this((IIdentity?)null, (IEnumerable?)null, (string?)null, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// supplies the and .
/// for details on how internal values are set.
public ClaimsIdentity(IIdentity? identity)
: this(identity, (IEnumerable?)null, (string?)null, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// associated with this instance.
///
/// for details on how internal values are set.
///
public ClaimsIdentity(IEnumerable? claims)
: this((IIdentity?)null, claims, (string?)null, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// The authentication method used to establish this identity.
public ClaimsIdentity(string? authenticationType)
: this((IIdentity?)null, (IEnumerable?)null, authenticationType, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// associated with this instance.
/// The authentication method used to establish this identity.
/// for details on how internal values are set.
public ClaimsIdentity(IEnumerable? claims, string? authenticationType)
: this((IIdentity?)null, claims, authenticationType, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// supplies the and .
/// associated with this instance.
/// for details on how internal values are set.
public ClaimsIdentity(IIdentity? identity, IEnumerable? claims)
: this(identity, claims, (string?)null, (string?)null, (string?)null)
{
}
///
/// Initializes an instance of .
///
/// The type of authentication used.
/// The used when obtaining the value of .
/// The used when performing logic for .
/// for details on how internal values are set.
public ClaimsIdentity(string? authenticationType, string? nameType, string? roleType)
: this((IIdentity?)null, (IEnumerable?)null, authenticationType, nameType, roleType)
{
}
///
/// Initializes an instance of .
///
/// associated with this instance.
/// The type of authentication used.
/// The used when obtaining the value of .
/// The used when performing logic for .
/// for details on how internal values are set.
public ClaimsIdentity(IEnumerable? claims, string? authenticationType, string? nameType, string? roleType)
: this((IIdentity?)null, claims, authenticationType, nameType, roleType)
{
}
///
/// Initializes an instance of .
///
/// supplies the and .
/// associated with this instance.
/// The type of authentication used.
/// The used when obtaining the value of .
/// The used when performing logic for .
/// If 'identity' is a , then there are potentially multiple sources for AuthenticationType, NameClaimType, RoleClaimType.
/// Priority is given to the parameters: authenticationType, nameClaimType, roleClaimType.
/// All s are copied into this instance in a . Each Claim is examined and if Claim.Subject != this, then Claim.Clone(this) is called before the claim is added.
/// Any 'External' claims are ignored.
///
/// if 'identity' is a and results in a circular reference back to 'this'.
public ClaimsIdentity(IIdentity? identity, IEnumerable? 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);
}
}
///
/// Initializes an instance of using a .
/// Normally the is constructed using the bytes from and initialized in the same way as the .
///
/// a pointing to a .
/// if 'reader' is null.
public ClaimsIdentity(BinaryReader reader)
{
if (reader == null)
throw new ArgumentNullException(nameof(reader));
Initialize(reader);
}
///
/// Copy constructor.
///
/// to copy.
/// if 'other' is null.
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();
}
///
/// Initializes an instance of from a serialized stream created via
/// .
///
///
/// The to read from.
///
/// Thrown is the is null.
protected ClaimsIdentity(SerializationInfo info)
{
throw new PlatformNotSupportedException();
}
///
/// Gets the authentication type that can be used to determine how this authenticated to an authority.
///
public virtual string? AuthenticationType
{
get { return _authenticationType; }
}
///
/// Gets a value that indicates if the user has been authenticated.
///
public virtual bool IsAuthenticated
{
get { return !string.IsNullOrEmpty(_authenticationType); }
}
///
/// Gets or sets a that was granted delegation rights.
///
/// if 'value' results in a circular reference back to 'this'.
public ClaimsIdentity? Actor
{
get { return _actor; }
set
{
if (value != null)
{
if (IsCircular(value))
{
throw new InvalidOperationException(SR.InvalidOperationException_ActorGraphCircular);
}
}
_actor = value;
}
}
///
/// Gets or sets a context that was used to create this .
///
public object? BootstrapContext
{
get { return _bootstrapContext; }
set { _bootstrapContext = value; }
}
///
/// Gets the claims as , associated with this .
///
/// May contain nulls.
public virtual IEnumerable Claims
{
get
{
if (_externalClaims == null)
{
return _instanceClaims;
}
return CombinedClaimsIterator();
}
}
private IEnumerable 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;
}
}
}
}
///
/// Contains any additional data provided by a derived type, typically set when calling .
///
protected virtual byte[]? CustomSerializationData
{
get
{
return _userSerializationData;
}
}
///
/// Allow the association of claims with this instance of .
/// The claims will not be serialized or added in Clone(). They will be included in searches, finds and returned from the call to .
///
internal List> ExternalClaims
{
get
{
if (_externalClaims == null)
{
_externalClaims = new List>();
}
return _externalClaims;
}
}
///
/// Gets or sets the label for this
///
public string? Label
{
get { return _label; }
set { _label = value; }
}
///
/// Gets the Name of this .
///
/// Calls where string == NameClaimType, if found, returns otherwise null.
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;
}
}
///
/// Gets the value that identifies 'Name' claims. This is used when returning the property .
///
public string NameClaimType
{
get { return _nameClaimType; }
}
///
/// Gets the value that identifies 'Role' claims. This is used when calling .
///
public string RoleClaimType
{
get { return _roleClaimType; }
}
///
/// Creates a new instance of with values copied from this object.
///
public virtual ClaimsIdentity Clone()
{
return new ClaimsIdentity(this);
}
///
/// Adds a single to an internal list.
///
/// the add.
/// If != this, then Claim.Clone(this) is called before the claim is added.
/// if 'claim' is null.
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));
}
}
///
/// Adds a to the internal list.
///
/// Enumeration of claims to add.
/// Each claim is examined and if != this, then Claim.Clone(this) is called before the claim is added.
/// if 'claims' is null.
public virtual void AddClaims(IEnumerable 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));
}
}
}
///
/// Attempts to remove a the internal list.
///
/// the to match.
/// It is possible that a returned from cannot be removed. This would be the case for 'External' claims that are provided by reference.
/// object.ReferenceEquals is used to 'match'.
///
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;
}
///
/// Removes a from the internal list.
///
/// the to match.
/// It is possible that a returned from cannot be removed. This would be the case for 'External' claims that are provided by reference.
/// object.ReferenceEquals is used to 'match'.
///
/// if 'claim' cannot be removed.
public virtual void RemoveClaim(Claim? claim)
{
if (!TryRemoveClaim(claim))
{
throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ClaimCannotBeRemoved, claim));
}
}
///
/// Adds claims to internal list. Calling Claim.Clone if Claim.Subject != this.
///
/// a to add to
/// private only call from constructor, adds to internal list.
private void SafeAddClaims(IEnumerable 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));
}
}
}
///
/// Adds claim to internal list. Calling Claim.Clone if Claim.Subject != this.
///
/// private only call from constructor, adds to internal list.
private void SafeAddClaim(Claim? claim)
{
if (claim == null)
return;
if (object.ReferenceEquals(claim.Subject, this))
{
_instanceClaims.Add(claim);
}
else
{
_instanceClaims.Add(claim.Clone(this));
}
}
///
/// Retrieves a where each claim is matched by .
///
/// The function that performs the matching logic.
/// A of matched claims.
/// if 'match' is null.
public virtual IEnumerable FindAll(Predicate match)
{
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
foreach (Claim claim in Claims)
{
if (match(claim))
{
yield return claim;
}
}
}
///
/// Retrieves a where each Claim.Type equals .
///
/// The type of the claim to match.
/// A of matched claims.
/// Comparison is: StringComparison.OrdinalIgnoreCase.
/// if 'type' is null.
public virtual IEnumerable 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;
}
}
}
}
///
/// Retrieves the first that is matched by .
///
/// The function that performs the matching logic.
/// A , null if nothing matches.
/// if 'match' is null.
public virtual Claim? FindFirst(Predicate match)
{
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
foreach (Claim claim in Claims)
{
if (match(claim))
{
return claim;
}
}
return null;
}
///
/// Retrieves the first where Claim.Type equals .
///
/// The type of the claim to match.
/// A , null if nothing matches.
/// Comparison is: StringComparison.OrdinalIgnoreCase.
/// if 'type' is null.
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;
}
///
/// Determines if a claim is contained within this ClaimsIdentity.
///
/// The function that performs the matching logic.
/// true if a claim is found, false otherwise.
/// if 'match' is null.
public virtual bool HasClaim(Predicate match)
{
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
foreach (Claim claim in Claims)
{
if (match(claim))
{
return true;
}
}
return false;
}
///
/// Determines if a claim with type AND value is contained within this ClaimsIdentity.
///
/// the type of the claim to match.
/// the value of the claim to match.
/// true if a claim is matched, false otherwise.
/// Comparison is: StringComparison.OrdinalIgnoreCase for Claim.Type, StringComparison.Ordinal for Claim.Value.
/// if 'type' is null.
/// if 'value' is null.
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;
}
///
/// Initializes from a . Normally the reader is initialized with the results from
/// Normally the is initialized in the same way as the passed to .
///
/// a pointing to a .
/// if 'reader' is null.
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();
}
}
///
/// Provides an extensibility point for derived types to create a custom .
///
/// the that points at the claim.
/// a new .
protected virtual Claim CreateClaim(BinaryReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
return new Claim(reader, this);
}
///
/// Serializes using a
///
/// the to use for data storage.
/// if 'writer' is null.
public virtual void WriteTo(BinaryWriter writer)
{
WriteTo(writer, null);
}
///
/// Serializes using a
///
/// the to use for data storage.
/// additional data provided by derived type.
/// if 'writer' is null.
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();
}
///
/// Checks if a circular reference exists to 'this'
///
///
///
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;
}
///
/// Populates the specified with the serialization data for the ClaimsIdentity
///
/// The serialization information stream to write to. Satisfies ISerializable contract.
/// Context for serialization. Can be null.
/// Thrown if the info parameter is null.
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;
}
///
/// Returns a new instance of with values copied from this object.
///
public override ClaimsIdentity Clone()
{
return new GenericIdentity(this);
}
public override IEnumerable 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 -
多个身份会有一个首要身份,使用
FuncSelectPrimaryIdentity来进行查找, ClaimsIdentity?> -
////// Gets the identity of the current principal. /// public virtual System.Security.Principal.IIdentity? Identity { get { if (s_identitySelector != null) { return s_identitySelector(_identities); } else { return SelectPrimaryIdentity(_identities); } } } ////// This method iterates through the collection of ClaimsIdentities and chooses an identity as the primary. /// private static ClaimsIdentity? SelectPrimaryIdentity(IEnumerableidentities) { if (identities == null) { throw new ArgumentNullException(nameof(identities)); } foreach (ClaimsIdentity identity in identities) { if (identity != null) { return identity; } } return null; }
///
/// Concrete IPrincipal supporting multiple claims-based identities
///
public class ClaimsPrincipal : IPrincipal
{
private enum SerializationMask
{
None = 0,
HasIdentities = 1,
UserData = 2
}
private readonly List _identities = new List();
private readonly byte[]? _userSerializationData;
private static Func, ClaimsIdentity?> s_identitySelector = SelectPrimaryIdentity;
private static Func 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();
}
///
/// This method iterates through the collection of ClaimsIdentities and chooses an identity as the primary.
///
private static ClaimsIdentity? SelectPrimaryIdentity(IEnumerable identities)
{
if (identities == null)
{
throw new ArgumentNullException(nameof(identities));
}
foreach (ClaimsIdentity identity in identities)
{
if (identity != null)
{
return identity;
}
}
return null;
}
public static Func, ClaimsIdentity?> PrimaryIdentitySelector
{
get
{
return s_identitySelector;
}
set
{
s_identitySelector = value;
}
}
public static Func ClaimsPrincipalSelector
{
get
{
return s_principalSelector;
}
set
{
s_principalSelector = value;
}
}
///
/// Initializes an instance of .
///
public ClaimsPrincipal()
{
}
///
/// Initializes an instance of .
///
/// the subjects in the principal.
/// if 'identities' is null.
public ClaimsPrincipal(IEnumerable identities)
{
if (identities == null)
{
throw new ArgumentNullException(nameof(identities));
}
_identities.AddRange(identities);
}
///
/// Initializes an instance of
///
/// representing the subject in the principal.
/// if 'identity' is null.
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));
}
}
///
/// Initializes an instance of
///
/// used to form this instance.
/// if 'principal' is null.
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);
}
}
}
///
/// Initializes an instance of using a .
/// Normally the is constructed using the bytes from and initialized in the same way as the .
///
/// a pointing to a .
/// if 'reader' is null.
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();
}
}
///
/// Adds a single to an internal list.
///
/// the add.
/// if 'identity' is null.
public virtual void AddIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
_identities.Add(identity);
}
///
/// Adds a to the internal list.
///
/// Enumeration of ClaimsIdentities to add.
/// if 'identities' is null.
public virtual void AddIdentities(IEnumerable identities)
{
if (identities == null)
{
throw new ArgumentNullException(nameof(identities));
}
_identities.AddRange(identities);
}
///
/// Gets the claims as , associated with this by enumerating all .
///
public virtual IEnumerable Claims
{
get
{
foreach (ClaimsIdentity identity in Identities)
{
foreach (Claim claim in identity.Claims)
{
yield return claim;
}
}
}
}
///
/// Contains any additional data provided by derived type, typically set when calling .
///
protected virtual byte[]? CustomSerializationData
{
get
{
return _userSerializationData;
}
}
///
/// Creates a new instance of with values copied from this object.
///
public virtual ClaimsPrincipal Clone()
{
return new ClaimsPrincipal(this);
}
///
/// Provides an extensibility point for derived types to create a custom .
///
/// the that points at the claim.
/// if 'reader' is null.
/// a new .
protected virtual ClaimsIdentity CreateClaimsIdentity(BinaryReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
return new ClaimsIdentity(reader);
}
///
/// Returns the Current Principal by calling a delegate. Users may specify the delegate.
///
public static ClaimsPrincipal? Current
{
// just accesses the current selected principal selector, doesn't set
get
{
return s_principalSelector is not null ? s_principalSelector() : SelectClaimsPrincipal();
}
}
///
/// Retrieves a where each claim is matched by .
///
/// The predicate that performs the matching logic.
/// A of matched claims.
/// Each is called. .
/// if 'match' is null.
public virtual IEnumerable FindAll(Predicate 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;
}
}
}
}
///
/// Retrieves a where each Claim.Type equals .
///
/// The type of the claim to match.
/// A of matched claims.
/// Each is called. .
/// if 'type' is null.
public virtual IEnumerable 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;
}
}
}
}
///
/// Retrieves the first that is matched by .
///
/// The predicate that performs the matching logic.
/// A , null if nothing matches.
/// Each is called. .
/// if 'match' is null.
public virtual Claim? FindFirst(Predicate 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;
}
///
/// Retrieves the first where the Claim.Type equals .
///
/// The type of the claim to match.
/// A , null if nothing matches.
/// Each is called. .
/// if 'type' is null.
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;
}
///
/// Determines if a claim is contained within all the ClaimsIdentities in this ClaimPrincipal.
///
/// The predicate that performs the matching logic.
/// true if a claim is found, false otherwise.
/// Each is called. .
/// if 'match' is null.
public virtual bool HasClaim(Predicate 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;
}
///
/// Determines if a claim of claimType AND claimValue exists in any of the identities.
///
/// the type of the claim to match.
/// the value of the claim to match.
/// true if a claim is matched, false otherwise.
/// Each is called. .
/// if 'type' is null.
/// if 'value' is null.
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;
}
///
/// Collection of
///
public virtual IEnumerable Identities
{
get
{
return _identities;
}
}
///
/// Gets the identity of the current principal.
///
public virtual System.Security.Principal.IIdentity? Identity
{
get
{
if (s_identitySelector != null)
{
return s_identitySelector(_identities);
}
else
{
return SelectPrimaryIdentity(_identities);
}
}
}
///
/// IsInRole answers the question: does an identity this principal possesses
/// contain a claim of type RoleClaimType where the value is '==' to the role.
///
/// The role to check for.
/// 'True' if a claim is found. Otherwise 'False'.
/// Each Identity has its own definition of the ClaimType that represents a role.
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;
}
///
/// Serializes using a
///
/// if 'writer' is null.
public virtual void WriteTo(BinaryWriter writer)
{
WriteTo(writer, null);
}
///
/// Serializes using a
///
/// the to use for data storage.
/// additional data provided by derived type.
/// if 'writer' is null.
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);
}
///
/// helper method to add roles
///
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 roleClaims = new List(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
///
/// Contains user identity information as well as additional authentication state.
///
public class AuthenticationTicket
{
///
/// Initializes a new instance of the class
///
/// the that represents the authenticated user.
/// additional properties that can be consumed by the user or runtime.
/// the authentication scheme that was responsible for this ticket.
public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties? properties, string authenticationScheme)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
AuthenticationScheme = authenticationScheme;
Principal = principal;
Properties = properties ?? new AuthenticationProperties();
}
///
/// Initializes a new instance of the class
///
/// the that represents the authenticated user.
/// the authentication scheme that was responsible for this ticket.
public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme)
: this(principal, properties: null, authenticationScheme: authenticationScheme)
{ }
///
/// Gets the authentication scheme that was responsible for this ticket.
///
public string AuthenticationScheme { get; }
///
/// Gets the claims-principal with authenticated user identities.
///
public ClaimsPrincipal Principal { get; }
///
/// Additional state values for the authentication session.
///
public AuthenticationProperties Properties { get; }
///
/// Returns a copy of the ticket.
///
///
/// The method clones the by calling on each of the .
///
/// A copy of the ticket
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)相关的信息,大部分属性都是对票据的描述。
///
/// Dictionary used to store state values about the authentication session.
///
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";
///
/// Initializes a new instance of the class.
///
public AuthenticationProperties()
: this(items: null, parameters: null)
{ }
///
/// Initializes a new instance of the class.
///
/// State values dictionary to use.
public AuthenticationProperties(IDictionary items)
: this(items, parameters: null)
{ }
///
/// Initializes a new instance of the class.
///
/// State values dictionary to use.
/// Parameters dictionary to use.
public AuthenticationProperties(IDictionary? items, IDictionary? parameters)
{
Items = items ?? new Dictionary(StringComparer.Ordinal);
Parameters = parameters ?? new Dictionary(StringComparer.Ordinal);
}
///
/// Return a copy.
///
/// A copy.
public AuthenticationProperties Clone()
=> new AuthenticationProperties(
new Dictionary(Items, StringComparer.Ordinal),
new Dictionary(Parameters, StringComparer.Ordinal));
///
/// State values about the authentication session.
///
public IDictionary Items { get; }
///
/// 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.
///
public IDictionary Parameters { get; }
///
/// Gets or sets whether the authentication session is persisted across multiple requests.
///
public bool IsPersistent
{
get => GetString(IsPersistentKey) != null;
set => SetString(IsPersistentKey, value ? string.Empty : null);
}
///
/// Gets or sets the full path or absolute URI to be used as an http redirect response value.
///
public string? RedirectUri
{
get => GetString(RedirectUriKey);
set => SetString(RedirectUriKey, value);
}
///
/// Gets or sets the time at which the authentication ticket was issued.
///
public DateTimeOffset? IssuedUtc
{
get => GetDateTimeOffset(IssuedUtcKey);
set => SetDateTimeOffset(IssuedUtcKey, value);
}
///
/// Gets or sets the time at which the authentication ticket expires.
///
public DateTimeOffset? ExpiresUtc
{
get => GetDateTimeOffset(ExpiresUtcKey);
set => SetDateTimeOffset(ExpiresUtcKey, value);
}
///
/// Gets or sets if refreshing the authentication session should be allowed.
///
public bool? AllowRefresh
{
get => GetBool(RefreshKey);
set => SetBool(RefreshKey, value);
}
///
/// Get a string value from the collection.
///
/// Property key.
/// Retrieved value or null if the property is not set.
public string? GetString(string key)
{
return Items.TryGetValue(key, out var value) ? value : null;
}
///
/// Set or remove a string value from the collection.
///
/// Property key.
/// Value to set or to remove the property.
public void SetString(string key, string? value)
{
if (value != null)
{
Items[key] = value;
}
else
{
Items.Remove(key);
}
}
///
/// Get a parameter from the collection.
///
/// Parameter type.
/// Parameter key.
/// Retrieved value or the default value if the property is not set.
public T? GetParameter(string key)
=> Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default;
///
/// Set a parameter value in the collection.
///
/// Parameter type.
/// Parameter key.
/// Value to set.
public void SetParameter(string key, T value)
=> Parameters[key] = value;
///
/// Get a nullable from the collection.
///
/// Property key.
/// Retrieved value or if the property is not set.
protected bool? GetBool(string key)
{
if (Items.TryGetValue(key, out var value) && bool.TryParse(value, out var boolValue))
{
return boolValue;
}
return null;
}
///
/// Set or remove a value in the collection.
///
/// Property key.
/// Value to set or to remove the property.
protected void SetBool(string key, bool? value)
{
if (value.HasValue)
{
Items[key] = value.GetValueOrDefault().ToString();
}
else
{
Items.Remove(key);
}
}
///
/// Get a nullable value from the collection.
///
/// Property key.
/// Retrieved value or if the property is not set.
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;
}
///
/// Sets or removes a value in the collection.
///
/// Property key.
/// Value to set or to remove the property.
protected void SetDateTimeOffset(string key, DateTimeOffset? value)
{
if (value.HasValue)
{
Items[key] = value.GetValueOrDefault().ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
}
else
{
Items.Remove(key);
}
}
}
TicketDataFormat
对票据进行格式化处理
ISecureDataFormat
///
/// A contract for securing data.
///
/// The type of the data to protect.
public interface ISecureDataFormat
{
///
/// Protects the specified .
///
/// The value to protect
/// The data protected value.
string Protect(TData data);
///
/// Protects the specified for the specified .
///
/// The value to protect
/// The purpose.
/// A data protected value.
string Protect(TData data, string? purpose);
///
/// Unprotects the specified .
///
/// The data protected value.
/// An instance of .
[return: MaybeNull]
TData Unprotect(string protectedText);
///
/// Unprotects the specified using the specified .
///
/// The data protected value.
/// The purpose.
/// An instance of .
[return: MaybeNull]
TData Unprotect(string protectedText, string? purpose);
}
SecureDataFormat
///
/// An implementation for .
///
///
public class SecureDataFormat : ISecureDataFormat
{
private readonly IDataSerializer _serializer;
private readonly IDataProtector _protector;
///
/// Initializes a new instance of .
///
/// The .
/// The .
public SecureDataFormat(IDataSerializer serializer, IDataProtector protector)
{
_serializer = serializer;
_protector = protector;
}
///
public string Protect(TData data)
{
return Protect(data, purpose: null);
}
///
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);
}
///
[return: MaybeNull]
public TData Unprotect(string protectedText)
{
return Unprotect(protectedText, purpose: null);
}
///
[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
///
/// A instance to secure
/// .
///
public class TicketDataFormat : SecureDataFormat
{
///
/// Initializes a new instance of .
///
/// The .
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
///
/// Contract for serialzing authentication data.
///
/// The type of the model being serialized.
public interface IDataSerializer
{
///
/// Serializes the specified .
///
/// The value to serialize.
/// The serialized data.
byte[] Serialize(TModel model);
///
/// Deserializes the specified as an instance of type .
///
/// The bytes being deserialized.
/// The model.
[return: MaybeNull]
TModel Deserialize(byte[] data);
}
TiceketSerializer
// This MUST be kept in sync with Microsoft.Owin.Security.Interop.AspNetTicketSerializer
///
/// Serializes and deserializes instances.
///
public class TicketSerializer : IDataSerializer
{
private const string DefaultStringPlaceholder = "\0";
private const int FormatVersion = 5;
///
/// Gets the default implementation for .
///
public static TicketSerializer Default { get; } = new TicketSerializer();
///
public virtual byte[] Serialize(AuthenticationTicket ticket)
{
using (var memory = new MemoryStream())
{
using (var writer = new BinaryWriter(memory))
{
Write(writer, ticket);
}
return memory.ToArray();
}
}
///
public virtual AuthenticationTicket? Deserialize(byte[] data)
{
using (var memory = new MemoryStream(data))
{
using (var reader = new BinaryReader(memory))
{
return Read(reader);
}
}
}
///
/// Writes the using the specified .
///
/// The .
/// The .
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);
}
///
/// Writes the specified .
///
/// The .
/// The .
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);
}
}
///
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);
}
}
///
/// Reads an .
///
/// The .
/// The if the format is supported, otherwise .
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);
}
///
/// Reads a from a .
///
/// The .
/// The read .
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;
}
///
/// Reads a and adds it to the specified .
///
/// The .
/// The to add the claim to.
/// The read .
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):
TaskAuthenticateAsync(); - 登录(SignIn):
Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties); - 登出(SignOut):
Task SignOutAsync(AuthenticationProperties properties);
AuthenticationSchemeProvider
AuthenticationScheme
认证方案名称,认证处理器的类型,
///
/// AuthenticationSchemes assign a name to a specific
/// handlerType.
///
public class AuthenticationScheme
{
///
/// Initializes a new instance of .
///
/// The name for the authentication scheme.
/// The display name for the authentication scheme.
/// The type that handles this scheme.
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;
}
///
/// The name of the authentication scheme.
///
public string Name { get; }
///
/// The display name for the scheme. Null is valid and used for non user facing schemes.
///
public string? DisplayName { get; }
///
/// The type that handles this scheme.
///
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
public Type HandlerType { get; }
}
AuthenticateOptions
存放着AuthenticationSchemeBuilder
public class AuthenticationOptions
{
private readonly IList _schemes = new List();
///
/// Returns the schemes in the order they were added (important for request handling priority)
///
public IEnumerable Schemes => _schemes;
///
/// Maps schemes by name.
///
public IDictionary SchemeMap { get; } = new Dictionary(StringComparer.Ordinal);
///
/// Adds an .
///
/// The name of the scheme being added.
/// Configures the scheme.
public void AddScheme(string name, Action 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;
}
///
/// Adds an .
///
/// The responsible for the scheme.
/// The name of the scheme being added.
/// The display name for the scheme.
public void AddScheme(string name, string displayName) where THandler : IAuthenticationHandler
=> AddScheme(name, b =>
{
b.DisplayName = displayName;
b.HandlerType = typeof(THandler);
});
///
/// Used as the fallback default scheme for all the other defaults.
///
public string DefaultScheme { get; set; }
///
/// Used as the default scheme by .
///
public string DefaultAuthenticateScheme { get; set; }
///
/// Used as the default scheme by .
///
public string DefaultSignInScheme { get; set; }
///
/// Used as the default scheme by .
///
public string DefaultSignOutScheme { get; set; }
///
/// Used as the default scheme by .
///
public string DefaultChallengeScheme { get; set; }
///
/// Used as the default scheme by .
///
public string DefaultForbidScheme { get; set; }
///
/// If true, SignIn should throw if attempted with a ClaimsPrincipal.Identity.IsAuthenticated = false.
///
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
///
/// Responsible for managing what authenticationSchemes are supported.
///
public interface IAuthenticationSchemeProvider
{
///
/// Returns all currently registered s.
///
/// All currently registered s.
Task> GetAllSchemesAsync();
///
/// Returns the matching the name, or null.
///
/// The name of the authenticationScheme.
/// The scheme or null if not found.
Task GetSchemeAsync(string name);
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultAuthenticateSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultChallengeSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultForbidSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultSignInSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultSignOutSchemeAsync();
///
/// Registers a scheme for use by .
///
/// The scheme.
void AddScheme(AuthenticationScheme scheme);
///
/// Removes a scheme, preventing it from being used by .
///
/// The name of the authenticationScheme being removed.
void RemoveScheme(string name);
///
/// Returns the schemes in priority order for request handling.
///
/// The schemes in priority order for request handling
Task> GetRequestHandlerSchemesAsync();
}
AuthenticationSchemeProvider
通过AuthenticateOptions中的AuthenticationSchemeBuilder来获取AuthenticationScheme
///
/// Implements .
///
public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
{
///
/// Creates an instance of
/// using the specified ,
///
/// The options.
public AuthenticationSchemeProvider(IOptions options)
: this(options, new Dictionary(StringComparer.Ordinal))
{
}
///
/// Creates an instance of
/// using the specified and .
///
/// The options.
/// The dictionary used to store authentication schemes.
protected AuthenticationSchemeProvider(IOptions options, IDictionary schemes)
{
_options = options.Value;
_schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
_requestHandlers = new List();
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 _schemes;
private readonly List _requestHandlers;
// Used as a safe return value for enumeration apis
private IEnumerable _schemesCopy = Array.Empty();
private IEnumerable _requestHandlersCopy = Array.Empty();
private Task GetDefaultSchemeAsync()
=> _options.DefaultScheme != null
? GetSchemeAsync(_options.DefaultScheme)
: Task.FromResult(null);
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
public virtual Task GetDefaultAuthenticateSchemeAsync()
=> _options.DefaultAuthenticateScheme != null
? GetSchemeAsync(_options.DefaultAuthenticateScheme)
: GetDefaultSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
public virtual Task GetDefaultChallengeSchemeAsync()
=> _options.DefaultChallengeScheme != null
? GetSchemeAsync(_options.DefaultChallengeScheme)
: GetDefaultSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
public virtual Task GetDefaultForbidSchemeAsync()
=> _options.DefaultForbidScheme != null
? GetSchemeAsync(_options.DefaultForbidScheme)
: GetDefaultChallengeSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
public virtual Task GetDefaultSignInSchemeAsync()
=> _options.DefaultSignInScheme != null
? GetSchemeAsync(_options.DefaultSignInScheme)
: GetDefaultSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise this will fallback to if that supports sign out.
///
/// The scheme that will be used by default for .
public virtual Task GetDefaultSignOutSchemeAsync()
=> _options.DefaultSignOutScheme != null
? GetSchemeAsync(_options.DefaultSignOutScheme)
: GetDefaultSignInSchemeAsync();
///
/// Returns the matching the name, or null.
///
/// The name of the authenticationScheme.
/// The scheme or null if not found.
public virtual Task GetSchemeAsync(string name)
=> Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);
///
/// Returns the schemes in priority order for request handling.
///
/// The schemes in priority order for request handling
public virtual Task> GetRequestHandlerSchemesAsync()
=> Task.FromResult(_requestHandlersCopy);
///
/// Registers a scheme for use by .
///
/// The scheme.
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();
}
}
///
/// Removes a scheme, preventing it from being used by .
///
/// The name of the authenticationScheme being removed.
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> GetAllSchemesAsync()
=> Task.FromResult(_schemesCopy);
}
AuthenticationHandlerProvider
提供Handler,对应5种认证方法
IAuthenticationHandlerProvider
/// Responsible for managing what authenticationSchemes are supported.
///
public interface IAuthenticationSchemeProvider
{
///
/// Returns all currently registered s.
///
/// All currently registered s.
Task> GetAllSchemesAsync();
///
/// Returns the matching the name, or null.
///
/// The name of the authenticationScheme.
/// The scheme or null if not found.
Task GetSchemeAsync(string name);
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultAuthenticateSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultChallengeSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultForbidSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultSignInSchemeAsync();
///
/// Returns the scheme that will be used by default for .
/// This is typically specified via .
/// Otherwise, this will fallback to .
///
/// The scheme that will be used by default for .
Task GetDefaultSignOutSchemeAsync();
///
/// Registers a scheme for use by .
///
/// The scheme.
void AddScheme(AuthenticationScheme scheme);
///
/// Registers a scheme for use by .
///
/// The scheme.
/// true if the scheme was added successfully.
bool TryAddScheme(AuthenticationScheme scheme)
{
try
{
AddScheme(scheme);
return true;
}
catch {
return false;
}
}
///
/// Removes a scheme, preventing it from being used by .
///
/// The name of the authenticationScheme being removed.
void RemoveScheme(string name);
///
/// Returns the schemes in priority order for request handling.
///
/// The schemes in priority order for request handling
Task> GetRequestHandlerSchemesAsync();
}
AuthenticationHandlerProvider
通过AuthenticationScheme反射创建IAuthenticationHandler对象,并进行缓存
///
/// Implementation of .
///
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
///
/// Constructor.
///
/// The .
public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
}
///
/// The .
///
public IAuthenticationSchemeProvider Schemes { get; }
// handler instance cache, need to initialize once per request
private Dictionary _handlerMap = new Dictionary(StringComparer.Ordinal);
///
/// Returns the handler instance that will be used.
///
/// The context.
/// The name of the authentication scheme being handled.
/// The handler instance.
public async Task 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
///
/// Created per request to handle authentication for to a particular scheme.
///
public interface IAuthenticationHandler
{
///
/// The handler should initialize anything it needs from the request and scheme here.
///
/// The scheme.
/// The context.
///
Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
///
/// Authentication behavior.
///
/// The result.
Task AuthenticateAsync();
///
/// Challenge behavior.
///
/// The that contains the extra meta-data arriving with the authentication.
/// A task.
Task ChallengeAsync(AuthenticationProperties properties);
///
/// Forbid behavior.
///
/// The that contains the extra meta-data arriving with the authentication.
/// A task.
Task ForbidAsync(AuthenticationProperties properties);
}
AuthenticationHandler
public abstract class AuthenticationHandler : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
{
private Task _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()?.OriginalPath ?? Request.Path;
protected PathString OriginalPathBase => Context.Features.Get()?.OriginalPathBase ?? Request.PathBase;
protected ILogger Logger { get; }
protected UrlEncoder UrlEncoder { get; }
protected ISystemClock Clock { get; }
protected IOptionsMonitor OptionsMonitor { get; }
///
/// 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.
///
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 options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
{
Logger = logger.CreateLogger(this.GetType().FullName);
UrlEncoder = encoder;
Clock = clock;
OptionsMonitor = options;
}
///
/// Initialize the handler, resolve the options and validate them.
///
///
///
///
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();
}
///
/// Initializes the events object, called once per request by .
///
protected virtual async Task InitializeEventsAsync()
{
Events = Options.Events;
if (Options.EventsType != null)
{
Events = Context.RequestServices.GetRequiredService(Options.EventsType);
}
Events = Events ?? await CreateEventsAsync();
}
///
/// Creates a new instance of the events instance.
///
/// A new instance of the events instance.
protected virtual Task
AuthenticationSchemeOptions
认证处理器的配置
///
/// Contains the options used by the .
///
public class AuthenticationSchemeOptions
{
///
/// Check that the options are valid. Should throw an exception if things are not ok.
///
public virtual void Validate() { }
///
/// Checks that the options are valid for a specific scheme
///
/// The scheme being validated.
public virtual void Validate(string scheme)
=> Validate();
///
/// Gets or sets the issuer that should be used for any claims that are created
///
public string ClaimsIssuer { get; set; }
///
/// Instance used for events
///
public object Events { get; set; }
///
/// If set, will be used as the service type to get the Events instance instead of the property.
///
public Type EventsType { get; set; }
///
/// 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.
///
public string ForwardDefault { get; set; }
///
/// 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.
///
public string ForwardAuthenticate { get; set; }
///
/// 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.
///
public string ForwardChallenge { get; set; }
///
/// 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.
///
public string ForwardForbid { get; set; }
///
/// 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.
///
public string ForwardSignIn { get; set; }
///
/// 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.
///
public string ForwardSignOut { get; set; }
///
/// 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.
///
public Func ForwardDefaultSelector { get; set; }
}
SignOutAuthenticationHandler
///
/// Used to determine if a handler supports SignOut.
///
public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
///
/// Signout behavior.
///
/// The that contains the extra meta-data arriving with the authentication.
/// A task.
Task SignOutAsync(AuthenticationProperties properties);
}
///
/// Adds support for SignOutAsync
///
public abstract class SignOutAuthenticationHandler : AuthenticationHandler, IAuthenticationSignOutHandler
where TOptions : AuthenticationSchemeOptions, new()
{
public SignOutAuthenticationHandler(IOptionsMonitor 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());
}
///
/// Override this method to handle SignOut.
///
///
/// A Task.
protected abstract Task HandleSignOutAsync(AuthenticationProperties properties);
}
SignInAuthenticationHandler
///
/// Used to determine if a handler supports SignIn.
///
public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
{
///
/// Handle sign in.
///
/// The user.
/// The that contains the extra meta-data arriving with the authentication.
/// A task.
Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}
///
/// Adds support for SignInAsync
///
public abstract class SignInAuthenticationHandler : SignOutAuthenticationHandler, IAuthenticationSignInHandler
where TOptions : AuthenticationSchemeOptions, new()
{
public SignInAuthenticationHandler(IOptionsMonitor 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());
}
///
/// Override this method to handle SignIn.
///
///
///
/// A Task.
protected abstract Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}
AuthenticationService
认证服务
///
/// Implements .
///
public class AuthenticationService : IAuthenticationService
{
///
/// Constructor.
///
/// The .
/// The .
/// The .
/// The .
public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions options)
{
Schemes = schemes;
Handlers = handlers;
Transform = transform;
Options = options.Value;
}
///
/// Used to lookup AuthenticationSchemes.
///
public IAuthenticationSchemeProvider Schemes { get; }
///
/// Used to resolve IAuthenticationHandler instances.
///
public IAuthenticationHandlerProvider Handlers { get; }
///
/// Used for claims transformation.
///
public IClaimsTransformation Transform { get; }
///
/// The .
///
public AuthenticationOptions Options { get; }
///
/// Authenticate for the specified authentication scheme.
///
/// The .
/// The name of the authentication scheme.
/// The result.
public virtual async Task 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 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;
}
///
/// Challenge the specified authentication scheme.
///
/// The .
/// The name of the authentication scheme.
/// The .
/// A task.
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 configureOptions).");
}
}
var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null)
{
throw await CreateMissingHandlerException(scheme);
}
await handler.ChallengeAsync(properties);
}
///
/// Forbid the specified authentication scheme.
///
/// The .
/// The name of the authentication scheme.
/// The .
/// A task.
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 configureOptions).");
}
}
var handler = await Handlers.GetHandlerAsync(context, scheme);
if (handler == null)
{
throw await CreateMissingHandlerException(scheme);
}
await handler.ForbidAsync(properties);
}
///
/// Sign a principal in for the specified authentication scheme.
///
/// The .
/// The name of the authentication scheme.
/// The to sign in.
/// The .
/// A task.
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 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);
}
///
/// Sign out the specified authentication scheme.
///
/// The .
/// The name of the authentication scheme.
/// The .
/// A task.
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 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 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 GetAllSignInSchemeNames()
{
return string.Join(", ", (await Schemes.GetAllSchemesAsync())
.Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
.Select(sch => sch.Name));
}
private async Task 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 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 GetAllSignOutSchemeNames()
{
return string.Join(", ", (await Schemes.GetAllSchemesAsync())
.Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
.Select(sch => sch.Name));
}
private async Task 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 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
///
/// Middleware that performs authentication.
///
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
///
/// Initializes a new instance of .
///
/// The next item in the middleware pipeline.
/// The .
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;
}
///
/// Gets or sets the .
///
public IAuthenticationSchemeProvider Schemes { get; set; }
///
/// Invokes the middleware performing authentication.
///
/// The .
public async Task Invoke(HttpContext context)
{
context.Features.Set(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();
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
///
/// Extension methods to expose Authentication on HttpContext.
///
public static class AuthenticationHttpContextExtensions
{
///
/// Extension method for authenticate using the scheme.
///
/// The context.
/// The .
public static Task AuthenticateAsync(this HttpContext context) =>
context.AuthenticateAsync(scheme: null);
///
/// Extension method for authenticate.
///
/// The context.
/// The name of the authentication scheme.
/// The .
public static Task AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService().AuthenticateAsync(context, scheme);
///
/// Extension method for Challenge.
///
/// The context.
/// The name of the authentication scheme.
/// The result.
public static Task ChallengeAsync(this HttpContext context, string scheme) =>
context.ChallengeAsync(scheme, properties: null);
///
/// Extension method for authenticate using the scheme.
///
/// The context.
/// The task.
public static Task ChallengeAsync(this HttpContext context) =>
context.ChallengeAsync(scheme: null, properties: null);
///
/// Extension method for authenticate using the scheme.
///
/// The context.
/// The properties.
/// The task.
public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties) =>
context.ChallengeAsync(scheme: null, properties: properties);
///
/// Extension method for Challenge.
///
/// The context.
/// The name of the authentication scheme.
/// The properties.
/// The task.
public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
context.RequestServices.GetRequiredService().ChallengeAsync(context, scheme, properties);
///
/// Extension method for Forbid.
///
/// The context.
/// The name of the authentication scheme.
/// The task.
public static Task ForbidAsync(this HttpContext context, string scheme) =>
context.ForbidAsync(scheme, properties: null);
///
/// Extension method for Forbid using the scheme..
///
/// The context.
/// The task.
public static Task ForbidAsync(this HttpContext context) =>
context.ForbidAsync(scheme: null, properties: null);
///
/// Extension method for Forbid.
///
/// The context.
/// The properties.
/// The task.
public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties) =>
context.ForbidAsync(scheme: null, properties: properties);
///
/// Extension method for Forbid.
///
/// The context.
/// The name of the authentication scheme.
/// The properties.
/// The task.
public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
context.RequestServices.GetRequiredService().ForbidAsync(context, scheme, properties);
///
/// Extension method for SignIn.
///
/// The context.
/// The name of the authentication scheme.
/// The user.
/// The task.
public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) =>
context.SignInAsync(scheme, principal, properties: null);
///
/// Extension method for SignIn using the .
///
/// The context.
/// The user.
/// The task.
public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) =>
context.SignInAsync(scheme: null, principal: principal, properties: null);
///
/// Extension method for SignIn using the .
///
/// The context.
/// The user.
/// The properties.
/// The task.
public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties) =>
context.SignInAsync(scheme: null, principal: principal, properties: properties);
///
/// Extension method for SignIn.
///
/// The context.
/// The name of the authentication scheme.
/// The user.
/// The properties.
/// The task.
public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) =>
context.RequestServices.GetRequiredService().SignInAsync(context, scheme, principal, properties);
///
/// Extension method for SignOut using the .
///
/// The context.
/// The task.
public static Task SignOutAsync(this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);
///
/// Extension method for SignOut using the .
///
/// The context.
/// The properties.
/// The task.
public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties) => context.SignOutAsync(scheme: null, properties: properties);
///
/// Extension method for SignOut.
///
/// The context.
/// The name of the authentication scheme.
/// The task.
public static Task SignOutAsync(this HttpContext context, string scheme) => context.SignOutAsync(scheme, properties: null);
///
/// Extension method for SignOut.
///
/// The context.
/// The name of the authentication scheme.
/// The properties.
/// The task.
public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
context.RequestServices.GetRequiredService().SignOutAsync(context, scheme, properties);
///
/// Extension method for getting the value of an authentication token.
///
/// The context.
/// The name of the authentication scheme.
/// The name of the token.
/// The value of the token.
public static Task GetTokenAsync(this HttpContext context, string scheme, string tokenName) =>
context.RequestServices.GetRequiredService().GetTokenAsync(context, scheme, tokenName);
///
/// Extension method for getting the value of an authentication token.
///
/// The context.
/// The name of the token.
/// The value of the token.
public static Task GetTokenAsync(this HttpContext context, string tokenName) =>
context.RequestServices.GetRequiredService().GetTokenAsync(context, tokenName);
依赖注入
AuthenticationCoreServiceCollectionExtensions
///
/// Extension methods for setting up authentication services in an .
///
public static class AuthenticationCoreServiceCollectionExtensions
{
///
/// Add core authentication services needed for .
///
/// The .
/// The service collection.
public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAddScoped();
services.TryAddSingleton(); // Can be replaced with scoped ones that use DbContext
services.TryAddScoped();
services.TryAddSingleton();
return services;
}
///
/// Add core authentication services needed for .
///
/// The .
/// Used to configure the .
/// The service collection.
public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action 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
///
/// Extension methods to add authentication capabilities to an HTTP application pipeline.
///
public static class AuthAppBuilderExtensions
{
///
/// Adds the to the specified , which enables authentication capabilities.
///
/// The to add the middleware to.
/// A reference to this instance after the operation has completed.
public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware();
}
}