ABP ABPAuthorizeModule
ABP ABPAuthorizeModule
2023/6/1
➡️

asp.net 认证和授权

微软 System.Security.Claims 程序集下定义声明

Claim 表示的是 用户的某个信息最小片段

//只摘取部分字段
 public class Claim
{
    //保存序列化相关的数据
    private readonly byte[] _userSerializationData;
    //用于标识声明的来源
    private readonly string _issuer;
    //用于标识声明最初的发行者。在某些情况下,声明可能会被中间方重新发行,
    //原始发行者字段可以追踪声明的源头
    private readonly string _originalIssuer;
    //用于存储与声明相关的其他信息。这些属性可以根据需要进行自定义
    private Dictionary<string, string> _properties;
    //主体是 System.Security.Claims.ClaimsIdentity 类的实例,它包含了一组声明。
    private readonly ClaimsIdentity _subject;
    //用于标识声明的含义。例如,"name" 表示用户的姓名,"role" 表示用户的角色
    private readonly string _type;
    //用于存储具体的信息。例如,如果类型为 "name",那么值可能是用户的实际姓名
    private readonly string _value;
    //因为 _value 被写成字符串,但实际 _value 的数据类型不一定是字符串,
    //所以使用此字段注明 _value 的数据类型
    private readonly string _valueType;
}

微软自己定义的内置 ClaimTypes

public static class ClaimTypes
{
    internal const string ClaimTypeNamespace
    = "http://schemas.microsoft.com/ws/2008/06/identity/claims";

    public const string AuthenticationInstant
    = ClaimTypeNamespace + "/authenticationinstant";
    public const string AuthenticationMethod
    = ClaimTypeNamespace + "/authenticationmethod";
    public const string CookiePath = ClaimTypeNamespace + "/cookiepath";
    public const string DenyOnlyPrimarySid =
    ClaimTypeNamespace + "/denyonlyprimarysid";
    public const string DenyOnlyPrimaryGroupSid =
    ClaimTypeNamespace + "/denyonlyprimarygroupsid";
    public const string DenyOnlyWindowsDeviceGroup =
    ClaimTypeNamespace + "/denyonlywindowsdevicegroup";
    public const string Dsa = ClaimTypeNamespace + "/dsa";
    public const string Expiration = ClaimTypeNamespace + "/expiration";
    public const string Expired = ClaimTypeNamespace + "/expired";
    public const string GroupSid = ClaimTypeNamespace + "/groupsid";
    public const string IsPersistent = ClaimTypeNamespace + "/ispersistent";
    public const string PrimaryGroupSid = ClaimTypeNamespace + "/primarygroupsid";
    public const string PrimarySid = ClaimTypeNamespace + "/primarysid";
    public const string Role = ClaimTypeNamespace + "/role";
    public const string SerialNumber = ClaimTypeNamespace + "/serialnumber";
    public const string UserData = ClaimTypeNamespace + "/userdata";
    public const string Version = ClaimTypeNamespace + "/version";
    public const string WindowsAccountName = ClaimTypeNamespace + "/windowsaccountname";
    public const string WindowsDeviceClaim = ClaimTypeNamespace + "/windowsdeviceclaim";
    public const string WindowsDeviceGroup = ClaimTypeNamespace + "/windowsdevicegroup";
    public const string WindowsUserClaim = ClaimTypeNamespace + "/windowsuserclaim";
    public const string WindowsFqbnVersion = ClaimTypeNamespace + "/windowsfqbnversion";
    public const string WindowsSubAuthority = ClaimTypeNamespace + "/windowssubauthority";


    //
    // From System.IdentityModel.Claims
    //
    internal const string ClaimType2005Namespace
    = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";

    public const string Anonymous = ClaimType2005Namespace + "/anonymous";
    public const string Authentication = ClaimType2005Namespace + "/authentication";
    public const string AuthorizationDecision =
    ClaimType2005Namespace + "/authorizationdecision";
    public const string Country = ClaimType2005Namespace + "/country";
    public const string DateOfBirth = ClaimType2005Namespace + "/dateofbirth";
    public const string Dns = ClaimType2005Namespace + "/dns";
    public const string DenyOnlySid = ClaimType2005Namespace + "/denyonlysid";

    public const string Email = ClaimType2005Namespace + "/emailaddress";
    public const string Gender = ClaimType2005Namespace + "/gender";
    public const string GivenName = ClaimType2005Namespace + "/givenname";
    public const string Hash = ClaimType2005Namespace + "/hash";
    public const string HomePhone = ClaimType2005Namespace + "/homephone";
    public const string Locality = ClaimType2005Namespace + "/locality";
    public const string MobilePhone = ClaimType2005Namespace + "/mobilephone";
    public const string Name = ClaimType2005Namespace + "/name";
    public const string NameIdentifier = ClaimType2005Namespace + "/nameidentifier";
    public const string OtherPhone = ClaimType2005Namespace + "/otherphone";
    public const string PostalCode = ClaimType2005Namespace + "/postalcode";
    public const string Rsa = ClaimType2005Namespace + "/rsa";
    public const string Sid = ClaimType2005Namespace + "/sid";
    public const string Spn = ClaimType2005Namespace + "/spn";
    public const string StateOrProvince = ClaimType2005Namespace + "/stateorprovince";
    public const string StreetAddress = ClaimType2005Namespace + "/streetaddress";
    public const string Surname = ClaimType2005Namespace + "/surname";
    public const string System = ClaimType2005Namespace + "/system";
    public const string Thumbprint = ClaimType2005Namespace + "/thumbprint";
    public const string Upn = ClaimType2005Namespace + "/upn";
    public const string Uri = ClaimType2005Namespace + "/uri";
    public const string Webpage = ClaimType2005Namespace + "/webpage";
    public const string X500DistinguishedName =
    ClaimType2005Namespace + "/x500distinguishedname";

    internal const string ClaimType2009Namespace =
     "http://schemas.xmlsoap.org/ws/2009/09/identity/claims";
    public const string Actor = ClaimType2009Namespace + "/actor";
}

内置的 value 数据类型

 public static class ClaimValueTypes
{
    private const string XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";

    public const string Base64Binary = XmlSchemaNamespace + "#base64Binary";
    public const string Base64Octet = XmlSchemaNamespace + "#base64Octet";
    public const string Boolean = XmlSchemaNamespace + "#boolean";
    public const string Date = XmlSchemaNamespace + "#date";
    public const string DateTime = XmlSchemaNamespace + "#dateTime";
    public const string Double = XmlSchemaNamespace + "#double";
    public const string Fqbn = XmlSchemaNamespace + "#fqbn";
    public const string HexBinary = XmlSchemaNamespace + "#hexBinary";
    public const string Integer = XmlSchemaNamespace + "#integer";
    public const string Integer32 = XmlSchemaNamespace + "#integer32";
    public const string Integer64 = XmlSchemaNamespace + "#integer64";
    public const string Sid = XmlSchemaNamespace + "#sid";
    public const string String = XmlSchemaNamespace + "#string";
    public const string Time = XmlSchemaNamespace + "#time";
    public const string UInteger32 = XmlSchemaNamespace + "#uinteger32";
    public const string UInteger64 = XmlSchemaNamespace + "#uinteger64";

    private const string SoapSchemaNamespace = "http://schemas.xmlsoap.org/";

    public const string DnsName = SoapSchemaNamespace + "claims/dns";
    public const string Email =
    SoapSchemaNamespace + "ws/2005/05/identity/claims/emailaddress";
    public const string Rsa = SoapSchemaNamespace + "ws/2005/05/identity/claims/rsa";
    public const string UpnName = SoapSchemaNamespace + "claims/UPN";

    private const string XmlSignatureConstantsNamespace =
    "http://www.w3.org/2000/09/xmldsig#";

    public const string DsaKeyValue = XmlSignatureConstantsNamespace + "DSAKeyValue";
    public const string KeyInfo = XmlSignatureConstantsNamespace + "KeyInfo";
    public const string RsaKeyValue = XmlSignatureConstantsNamespace + "RSAKeyValue";

    private const string XQueryOperatorsNameSpace =
     "http://www.w3.org/TR/2002/WD-xquery-operators-20020816";

    public const string DaytimeDuration = XQueryOperatorsNameSpace + "#dayTimeDuration";
    public const string YearMonthDuration =
    XQueryOperatorsNameSpace + "#yearMonthDuration";

    private const string Xacml10Namespace = "urn:oasis:names:tc:xacml:1.0";

    public const string Rfc822Name = Xacml10Namespace + ":data-type:rfc822Name";
    public const string X500Name = Xacml10Namespace + ":data-type:x500Name";
}

IIdentity 身份

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; }
}
public class ClaimsIdentity : IIdentity
{
    private byte[] _userSerializationData;
    private ClaimsIdentity _actor;
    private string _authenticationType;
    private object _bootstrapContext;
    //表示一个身份,包含了一组声明
    private List<List<Claim>> _externalClaims;
    private string _label;
    private readonly List<Claim> _instanceClaims = new List<Claim>();
    private string _nameClaimType =
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
    private string _roleClaimType =
    "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";

    #nullable enable
    /// <summary>The default issuer; "LOCAL AUTHORITY".</summary>
    public const string DefaultIssuer = "LOCAL AUTHORITY";
    /// <summary>The default name claim type;
    ///<see cref="F:System.Security.Claims.ClaimTypes.Name" />.</summary>
    public const string DefaultNameClaimType =
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
    /// <summary>The default role claim type;
    ///<see cref="F:System.Security.Claims.ClaimTypes.Role" />.</summary>
    public const string DefaultRoleClaimType =
    "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
}

IPrincipal 持有人,主体

System.Security.Principal

  public interface IPrincipal
  {

    //持有人的身份
    IIdentity? Identity { get; }

    bool IsInRole(string role);
  }
public class ClaimsPrincipal : IPrincipal{
 //持有人拥有的所有身份
 private readonly List<ClaimsIdentity> _identities = new List<ClaimsIdentity>();
 public static ClaimsPrincipal? Current => ClaimsPrincipal.s_principalSelector == null
 ? ClaimsPrincipal.SelectClaimsPrincipal() : ClaimsPrincipal.s_principalSelector();
 //当前身份
 public virtual IIdentity? Identity => ClaimsPrincipal.s_identitySelector != null
 ? (IIdentity) ClaimsPrincipal.s_identitySelector(
  (IEnumerable<ClaimsIdentity>) this._identities)
  : (IIdentity) ClaimsPrincipal.SelectPrimaryIdentity(
    (IEnumerable<ClaimsIdentity>) this._identities);
}

Authentication 认证

Microsoft.AspNetCore.Authentication

AuthenticationScheme 用户或当事人持有的身份识别的方式方法,如果用户的身份保存在 cookie 里 和 Token 里需要使用不同的方法来进行识别 [Authorize] 会使用当前配置的 AuthenticationScheme 去进行认证,允许多套 scheme 并存,同时指定默认的 scheme,也可以在 [Authorize(AuthenticationSchemes = "Cookies")] 里指定明确的 scheme

常用的 JWT Token 需要使用微软提供的这俩个程序集

  1. Microsoft.IdentityModel.Tokens Microsoft.IdentityModel.Tokens 是一个用于处理令牌验证和 生成的库。它提供了一组用于生成和验证令牌的类和方法。这个库主要用于处理 JSON Web Tokens(JWT)。JWT 是一种用于在不同系统之间安全传输信息的标准。我们可以轻松地生成 JWT,并使用密钥对其进行验证。它提供了一些常用的算法,如 HMACSHA256 和 RSA,用于生成和 验证签名。此外,它还提供了一些辅助方法,用于解析和读取 JWT 中的信息。

  2. Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.AspNetCore.Authentication.JwtBearer 是一个用于在 ASP.NET Core 应用程序中处理 JWT 身份验证的库。它建立在 Microsoft.IdentityModel.Tokens 之上,并提供了一些额外的功能 ,以便在 ASP.NET Core 中轻松地集成 JWT 身份验证。我们可以通过配置身份验证中间件来启用 JWT 身份验证。它提供了一些选项,用于配置令牌验证的参数,如密钥、签名算法和令牌验证的位 置。此外,它还提供了一些事件,用于自定义身份验证过程,如在验证成功或失败时执行特定的操 作。

ASP.NET Core 的身份验证处理程序(Authentication Handler)是用于执行实际身份验证逻辑的关键 组件。每个身份验证方案通常会有一个相应的处理程序,负责验证用户的身份、生成身份令牌或票据, 并将用户标识保存在请求上下文中。这些处理程序的功能包括:

  1. 用户身份验证:验证用户提供的凭据,例如用户名和密码、令牌或其他身份验证信息。这可以包括 与数据库、外部身份提供者(例如 OAuth、OpenID Connect)、Windows 身份验证等的交互。
  2. 生成身份令牌:一旦用户成功验证,处理程序负责生成身份令牌或票据,用于标识已验证的用户。 这可以是一个包含用户信息的 Cookie、JWT 令牌或其他形式的标识符。
  3. 用户身份标识:处理程序将验证成功的用户的标识信息(通常是声明)保存在请求的用户上下文中 ,以便后续的授权和访问控制可以使用这些信息来确定用户是否有权限执行特定操作。
  4. 与外部身份提供者的交互:对于外部身份提供者(例如 Google、Facebook、GitHub 等),身份验 证处理程序负责与它们交互,验证用户的身份并获取用户信息。
  5. 令牌验证:对于基于令牌的身份验证方案,处理程序需要验证和解析令牌,确保其有效性和完整性 。
  6. 单点登录(Single Sign-On,SSO):对于 SSO 方案,身份验证处理程序可能需要与认证服务器进 行通信,以实现跨多个应用程序的单一身份验证。
  7. 失败处理:在验证失败的情况下,处理程序需要处理错误情况,例如重定向到登录页面、返回身份 验证失败消息等。
  8. 注销:一些身份验证处理程序还支持用户注销,使用户可以主动终止其会话。
public class AuthenticationScheme
{
    /// <summary>
    /// Initializes a new instance of
    <see cref="T:Microsoft.AspNetCore.Authentication.AuthenticationScheme" />.
    /// </summary>
    /// <param name="name">The name for the authentication scheme.</param>
    /// <param name="displayName">The display name for the authentication scheme.</param>
    /// <param name="handlerType">The
    /// <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandler" />
    /// type that handles this scheme.</param>

    // string name 认证的名称, Type  handlerType 具体的认证方法
    public AuthenticationScheme(string name, string? displayName,
      [DynamicallyAccessedMembers(
      DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType)
    {
      if (name == null)
        throw new ArgumentNullException(nameof (name));
      if (handlerType == (Type) null)
        throw new ArgumentNullException(nameof (handlerType));
      if (!typeof (IAuthenticationHandler).IsAssignableFrom(handlerType))
        throw new ArgumentException("handlerType must implement IAuthenticationHandler.");
      this.Name = name;
      this.HandlerType = handlerType;
      this.DisplayName = displayName;
    }

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

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

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    public Type HandlerType { get; }
}

微软内部是通过 AuthenticationSchemeBuilder ,构建的 AuthenticationScheme

//JwtBearerDefaults.AuthenticationScheme 必需要指定一个默认的 scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   // 通过 AuthenticationSchemeBuilder  添加多种认证 scheme
  .AddJwtBearer()
  .AddCookie();

指定默认 DefaultScheme ,的时候,可以单独指定 DefaultSignInScheme 登其他的 scheme, 这些单 独添加的 DefaultSignInScheme 需要 通过 AuthenticationSchemeBuilder 进行添加

builder.Services.AddAuthentication(options => {
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = "myAuthenticationSignInScheme";
    options.DefaultSignOutScheme= "myAuthenticationSignOutScheme";
  })
  .AddJwtBearer(options => {

  });//添加 myAuthenticationSignInScheme ,myAuthenticationSignOutScheme

添加的 各种认证 scheme 被加入此类,此类是个配置类

// <summary>
/// Options to configure authentication.
/// </summary>
public class AuthenticationOptions
{
    private readonly IList<AuthenticationSchemeBuilder> _schemes =
    new List<AuthenticationSchemeBuilder>();

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

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

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

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

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

    // Workaround for linker bug: https://github.com/dotnet/linker/issues/1981
    private readonly struct AddSchemeState
    {
        public AddSchemeState([DynamicallyAccessedMembers(
          DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType)
        {
            HandlerType = handlerType;
        }

        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
        public Type HandlerType { get; }
    }

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

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

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

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

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

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

    /// <summary>
    /// If true, SignIn should throw if attempted with a user is not authenticated.
    /// A user is considered authenticated if <see cref="ClaimsIdentity.IsAuthenticated"/>
    /// returns <see langword="true" /> for the <see cref="ClaimsPrincipal"/>
    /// associated with the HTTP request.
    /// </summary>
    public bool RequireAuthenticatedSignIn { get; set; } = true;

    /// <summary>
    /// If true, DefaultScheme will not automatically use a single registered scheme.
    /// </summary>
    private bool? _disableAutoDefaultScheme;
    internal bool DisableAutoDefaultScheme
    {
        get
        {
            if (!_disableAutoDefaultScheme.HasValue)
            {
                _disableAutoDefaultScheme =
                AppContext.TryGetSwitch(
                  "Microsoft.AspNetCore.Authentication.SuppressAutoDefaultScheme",
                  out var enabled) && enabled;
            }

            return _disableAutoDefaultScheme.Value;
        }
        set => _disableAutoDefaultScheme = value;
    }
}

IAuthenticationHandler 认证处理器

IAuthenticationHandler 是被注入的服务容器的 services

认证处理器分为三个接口时因为在有些情况下不需要登入和登出处理器,比如 oauth 认证

public interface IAuthenticationHandler
{

    Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);

    Task<AuthenticateResult> AuthenticateAsync();

    Task ChallengeAsync(AuthenticationProperties? properties);

    Task ForbidAsync(AuthenticationProperties? properties);
}

public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
    Task SignOutAsync(AuthenticationProperties? properties);
}

public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
{
    Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties);
}

//中间件里使用的认证
public interface IAuthenticationRequestHandler : IAuthenticationHandler
{
    /// <summary>
    /// Gets a value that determines if the request should stop being processed.
    /// <para>
    /// This feature is supported by the Authentication middleware
    /// which does not invoke any subsequent <see cref="IAuthenticationHandler"/>
    /// or middleware configured in the request pipeline
    /// if the handler returns <see langword="true" />.
    /// </para>
    /// </summary>
    /// <returns><see langword="true" /> if request processing should stop.</returns>
    Task<bool> HandleRequestAsync();
}

添加自定义认证处理器

services.AddAuthentication().AddScheme<AuthenticationSchemeOptions,
CustomAuthenticationHandler > ("mySchema", options=> {
});

public class CustomAuthenticationHandler :
AuthenticationHandler<AuthenticationSchemeOptions>
{

    public MyAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions>
    options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {}

    //重载执行验证方法,该方法需要提供一个验证结果,若验证成功则还需要提供用户信息。
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        //该自定义验证从http 头信息中获取 token 信息并进行验证
        var token = Request.Headers["token"];
        if (token == "valid token")
        {
            //验证成功后创建用户信息
            var claimsIdentity = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Name, "foo"),
                new Claim(ClaimTypes.Role, "admin")
            }, "roleIIentity");

            var principal = new ClaimsPrincipal(claimsIdentity);
            var ticket = new AuthenticationTicket(principal, this.Scheme.Name);

            //在后的流程里就可以使用 ticket 完成后续的授权工作
            //无论何种 认证 scheme 都要生成 ticket ,才能用于后面的 授权
            return AuthenticateResult.Success(ticket);
        }
        else
        {
            return AuthenticateResult.Fail("token is invalid");
        }
    }
}

微软提供的认证 scheme:

  1. Microsoft.AspNetCore.Authentication.Cookies
  2. Microsoft.AspNetCore.Authentication.OAuth
  3. Microsoft.AspNetCore.Authentication.AzureAD.UI
  4. Microsoft.AspNetCore.Authentication.AzureADB2C.UI
  5. Microsoft.AspNetCore.Authentication.Certificate
  6. Microsoft.AspNetCore.Authentication.Facebook
  7. Microsoft.AspNetCore.Authentication.Google
  8. Microsoft.AspNetCore.Authentication.JwtBearer
  9. Microsoft.AspNetCore.Authentication.Microsoft
  10. Microsoft.AspNetCore.Authentication.Negotiate
  11. Microsoft.AspNetCore.Authentication.OpenIdConnect
  12. Microsoft.AspNetCore.Authentication.Twitter
  13. Microsoft.AspNetCore.Authentication.WsFederation

如此多的认证 scheme , asp.net core 内部是通过 IAuthenticationService 的进行的认证

IAuthenticationService

public interface IAuthenticationService
{
    //验证操作负责基于当前请求的上下文,使用来自请求中的信息,例如请求头、Cookie
    //等等来构造用户标识。构建的结果是一个 AuthenticateResult 对象,
    //它指示了验证是否成功,如果成功的话,用户标识将可以在验证票据中找到。
    //验证在登入后生成的 ticket 的正确性
    Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string? scheme);
    /**
    在授权管理阶段,如果用户没有得到验证,但所期望访问的资源要求必须得到验证的时候,
    授权服务会发出质询。例如,当匿名用户访问受限资源的时候,
    或者当用户点击登录链接的时候。授权服务会通过质询来相应用户。
    1. 基于 Cookie 的验证会将用户重定向到登录页面
    2. 基于 JWT 的验证会返回一个带有 www-authenticate: bearer
    响应头的 401 响应来提醒客户端需要提供访问凭据
       质询操作应该让用户知道应该使用何种验证机制来访问请求的资源。
     */
    Task ChallengeAsync(HttpContext context, string? scheme,
    AuthenticationProperties? properties);
    /**
    在授权管理阶段,如果用户已经通过了验证,但是对于其访问的资源并没有得到许可,此时会使用拒绝操作。
    1. Cookie 验证模式下,已经登录但是没有访问权限的用户,被重定向到一个提示无权访问的页面
    2. JWT 验证模式下,返回 403
    拒绝访问处理应该让用户知道
    1.它已经通过了验证
    2.但是没有权限访问请求的资源
    */
    Task ForbidAsync(HttpContext context, string? scheme,
    AuthenticationProperties? properties);
    //用户登入成功形成一个票据 ticket, 此后用户再次进行系统会携带此 ticket
    Task SignInAsync(HttpContext context, string? scheme,
    ClaimsPrincipal principal, AuthenticationProperties? properties);
    //清除 cookie 或 token
    Task SignOutAsync(HttpContext context, string? scheme,
    AuthenticationProperties? properties);
}

namespace Microsoft.AspNetCore.Authentication
{
  public class AuthenticationService : IAuthenticationService
  {
    private HashSet<ClaimsPrincipal> _transformCache;

    public AuthenticationService(
      IAuthenticationSchemeProvider schemes,
      IAuthenticationHandlerProvider handlers,
      IClaimsTransformation transform,
      IOptions<AuthenticationOptions> options)
    {
      this.Schemes = schemes;
      this.Handlers = handlers;
      this.Transform = transform;
      this.Options = options.Value;
    }
    public IAuthenticationSchemeProvider Schemes { get; }

    public IAuthenticationHandlerProvider Handlers { get; }

    public IClaimsTransformation Transform { get; }

    public AuthenticationOptions Options { get; }

    public virtual async Task<AuthenticateResult> AuthenticateAsync(
      HttpContext context,
      string? scheme)
    {
      if (scheme == null)
      {
        scheme = (await this.Schemes.GetDefaultAuthenticateSchemeAsync())?.Name;
        if (scheme == null)
          throw new InvalidOperationException("No authenticationScheme was specified,
          and there was no DefaultAuthenticateScheme found.
          The default schemes can be set using either
          AddAuthentication(string defaultScheme) or
          AddAuthentication(Action<AuthenticationOptions> configureOptions).");
      }
      IAuthenticationHandler handlerAsync =
      await this.Handlers.GetHandlerAsync(context, scheme);

      if (handlerAsync == null)
        throw await this.CreateMissingHandlerException(scheme);
      AuthenticateResult result = await handlerAsync.AuthenticateAsync()
      ?? AuthenticateResult.NoResult();
      if (!result.Succeeded)
        return result;
      ClaimsPrincipal principal = result.Principal;
      bool flag = true;
      if (this._transformCache == null)
        this._transformCache = new HashSet<ClaimsPrincipal>();
      if (this._transformCache.Contains(principal))
        flag = false;
      if (flag)
      {
        principal = await this.Transform.TransformAsync(principal);
        this._transformCache.Add(principal);
      }
      return AuthenticateResult.Success(
        new AuthenticationTicket(principal, result.Properties,
        result.Ticket.AuthenticationScheme));
    }

    public virtual async Task ChallengeAsync(
      HttpContext context,
      string? scheme,
      AuthenticationProperties? properties)
    {
      if (scheme == null)
      {
        scheme = (await this.Schemes.GetDefaultChallengeSchemeAsync())?.Name;
        if (scheme == null)
          throw new InvalidOperationException("No authenticationScheme was specified, and
           there was no DefaultChallengeScheme found. The default schemes can be set
           using either AddAuthentication(string defaultScheme) or
           AddAuthentication(Action<AuthenticationOptions> configureOptions).");
      }
      IAuthenticationHandler handlerAsync = await this.Handlers.
      GetHandlerAsync(context, scheme);

      if (handlerAsync == null)
        throw await this.CreateMissingHandlerException(scheme);
      await handlerAsync.ChallengeAsync(properties);
    }
    public virtual async Task ForbidAsync(
      HttpContext context,
      string? scheme,
      AuthenticationProperties? properties)
    {
      if (scheme == null)
      {
        scheme = (await this.Schemes.GetDefaultForbidSchemeAsync())?.Name;
        if (scheme == null)
          throw new InvalidOperationException("No authenticationScheme was specified, and
           there was no DefaultForbidScheme found. The default schemes can be set using
            either AddAuthentication(string defaultScheme) or
            AddAuthentication(Action<AuthenticationOptions> configureOptions).");
      }
      IAuthenticationHandler handlerAsync =
      await this.Handlers.GetHandlerAsync(context, scheme);

      if (handlerAsync == null)
        throw await this.CreateMissingHandlerException(scheme);
      await handlerAsync.ForbidAsync(properties);
    }

    public virtual async Task SignInAsync(
      HttpContext context,
      string? scheme,
      ClaimsPrincipal principal,
      AuthenticationProperties? properties)
    {
      if (principal == null)
        throw new ArgumentNullException(nameof (principal));
      if (this.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)
      {
        scheme = (await this.Schemes.GetDefaultSignInSchemeAsync())?.Name;
        if (scheme == null)
          throw new InvalidOperationException("No authenticationScheme was specified,
           and there was no DefaultSignInScheme found. The default schemes can be
           set using either AddAuthentication(string defaultScheme) or
           AddAuthentication(Action<AuthenticationOptions> configureOptions).");
      }
      IAuthenticationHandler handlerAsync =
      await this.Handlers.GetHandlerAsync(context, scheme);
      if (handlerAsync == null)
        throw await this.CreateMissingSignInHandlerException(scheme);
      if (!(handlerAsync is IAuthenticationSignInHandler authenticationSignInHandler))
        throw await this.CreateMismatchedSignInHandlerException(scheme, handlerAsync);
      await authenticationSignInHandler.SignInAsync(principal, properties);
    }

    public virtual async Task SignOutAsync(
      HttpContext context,
      string? scheme,
      AuthenticationProperties? properties)
    {
      if (scheme == null)
      {
        scheme = (await this.Schemes.GetDefaultSignOutSchemeAsync())?.Name;
        if (scheme == null)
          throw new InvalidOperationException("No authenticationScheme was specified, and
          there was no DefaultSignOutScheme found. The default schemes can be set using
           either AddAuthentication(string defaultScheme) or
           AddAuthentication(Action<AuthenticationOptions> configureOptions).");
      }
      IAuthenticationHandler handlerAsync = await this.Handlers.
      GetHandlerAsync(context, scheme);

      if (handlerAsync == null)
        throw await this.CreateMissingSignOutHandlerException(scheme);
      if (!(handlerAsync is IAuthenticationSignOutHandler authenticationSignOutHandler))
        throw await this.CreateMismatchedSignOutHandlerException(scheme, handlerAsync);
      await authenticationSignOutHandler.SignOutAsync(properties);
    }


    private async Task<Exception> CreateMissingHandlerException(string scheme)
    {
      string str1 = string.Join(", ", (await this.Schemes.GetAllSchemesAsync())
      .Select<AuthenticationScheme, string>
          ((Func<AuthenticationScheme, string>) (sch => sch.Name)));
      string str2 =
       " Did you forget to call AddAuthentication().
       Add[SomeAuthHandler](\"" + scheme + "\",...)?";

      if (string.IsNullOrEmpty(str1))
        return (Exception) new InvalidOperationException(
          "No authentication handlers are registered." + str2);
      DefaultInterpolatedStringHandler interpolatedStringHandler =
      new DefaultInterpolatedStringHandler(88, 2);
      interpolatedStringHandler.
      AppendLiteral("No authentication handler is registered for the scheme '");
      interpolatedStringHandler.AppendFormatted(scheme);
      interpolatedStringHandler.AppendLiteral("'. The registered schemes are: ");
      interpolatedStringHandler.AppendFormatted(str1);
      interpolatedStringHandler.AppendLiteral(".");
      return (Exception)
      new InvalidOperationException(interpolatedStringHandler.ToStringAndClear() + str2);
    }

    private async Task<string> GetAllSignInSchemeNames() =>
     string.Join(", ", (await this.Schemes.GetAllSchemesAsync()).
     Where<AuthenticationScheme>((Func<AuthenticationScheme, bool>)
     (sch => typeof (IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))).
     Select<AuthenticationScheme, string>((Func<AuthenticationScheme, string>)
      (sch => sch.Name)));

    private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
    {
      string signInSchemeNames = await this.GetAllSignInSchemeNames();
      string str = " Did you forget to call AddAuthentication().
      AddCookie(\"" + scheme + "\",...)?";
      if (string.IsNullOrEmpty(signInSchemeNames))
        return (Exception) new InvalidOperationException(
          "No sign-in authentication handlers are registered." + str);
      DefaultInterpolatedStringHandler interpolatedStringHandler =
       new DefaultInterpolatedStringHandler(104, 2);
      interpolatedStringHandler.AppendLiteral(
        "No sign-in authentication handler is registered for the scheme '");
      interpolatedStringHandler.AppendFormatted(scheme);
      interpolatedStringHandler.AppendLiteral(
        "'. The registered sign-in schemes are: ");
      interpolatedStringHandler.AppendFormatted(signInSchemeNames);
      interpolatedStringHandler.AppendLiteral(".");
      return (Exception) new InvalidOperationException(
        interpolatedStringHandler.ToStringAndClear() + str);
    }

    private async Task<Exception> CreateMismatchedSignInHandlerException(
      string scheme,
      IAuthenticationHandler handler)
    {
      string signInSchemeNames = await this.GetAllSignInSchemeNames();
      DefaultInterpolatedStringHandler interpolatedStringHandler =
       new DefaultInterpolatedStringHandler(96, 2);
      interpolatedStringHandler.AppendLiteral(
        "The authentication handler registered for scheme '");
      interpolatedStringHandler.AppendFormatted(scheme);
      interpolatedStringHandler.AppendLiteral("' is '");
      interpolatedStringHandler.AppendFormatted(handler.GetType().Name);
      interpolatedStringHandler.AppendLiteral("' which cannot be used for SignInAsync. ");
      string stringAndClear = interpolatedStringHandler.ToStringAndClear();
      return !string.IsNullOrEmpty(signInSchemeNames) ?
      (Exception) new InvalidOperationException(
        stringAndClear + "The registered sign-in schemes are: " + signInSchemeNames + ".")
        : (Exception) new InvalidOperationException(
          stringAndClear +
           "Did you forget to call AddAuthentication().
           AddCookie(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
    }

    private async Task<string> GetAllSignOutSchemeNames() => string.Join(", ",
    (await this.Schemes.GetAllSchemesAsync()).Where<AuthenticationScheme>(
      (Func<AuthenticationScheme, bool>) (sch => typeof (IAuthenticationSignOutHandler).
      IsAssignableFrom(sch.HandlerType))).Select<AuthenticationScheme, string>
      ((Func<AuthenticationScheme, string>) (sch => sch.Name)));

    private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
    {
      string signOutSchemeNames = await this.GetAllSignOutSchemeNames();
      string str = " Did you forget to call AddAuthentication().AddCookie(\"" + scheme + "\",...)?";
      if (string.IsNullOrEmpty(signOutSchemeNames))
        return (Exception) new InvalidOperationException(
          "No sign-out authentication handlers are registered." + str);
      DefaultInterpolatedStringHandler interpolatedStringHandler =
      new DefaultInterpolatedStringHandler(106, 2);
      interpolatedStringHandler.AppendLiteral("No sign-out authentication handler
       is registered for the scheme '");
      interpolatedStringHandler.AppendFormatted(scheme);
      interpolatedStringHandler.AppendLiteral("'. The registered sign-out schemes are: ");
      interpolatedStringHandler.AppendFormatted(signOutSchemeNames);
      interpolatedStringHandler.AppendLiteral(".");
      return (Exception) new InvalidOperationException(
        interpolatedStringHandler.ToStringAndClear() + str);
    }

    private async Task<Exception> CreateMismatchedSignOutHandlerException(
      string scheme,
      IAuthenticationHandler handler)
    {
      string signOutSchemeNames = await this.GetAllSignOutSchemeNames();
      DefaultInterpolatedStringHandler interpolatedStringHandler =
      new DefaultInterpolatedStringHandler(85, 3);
      interpolatedStringHandler.AppendLiteral("The authentication handler registered for scheme '");
      interpolatedStringHandler.AppendFormatted(scheme);
      interpolatedStringHandler.AppendLiteral("' is '");
      interpolatedStringHandler.AppendFormatted(handler.GetType().Name);
      interpolatedStringHandler.AppendLiteral("' which cannot be used for ");
      interpolatedStringHandler.AppendFormatted("SignOutAsync");
      interpolatedStringHandler.AppendLiteral(". ");
      string stringAndClear = interpolatedStringHandler.ToStringAndClear();
      return !string.IsNullOrEmpty(signOutSchemeNames) ? (Exception)
      new InvalidOperationException(stringAndClear + "The registered sign-out schemes are:
       " + signOutSchemeNames + ".") : (Exception)
       new InvalidOperationException(stringAndClear +
       "Did you forget to call AddAuthentication().AddCookie(\"Cookies\")
        and SignOutAsync(\"Cookies\",...)?");
    }
  }
}

asp.net core 给 HttpContext 扩展的方法使用的就是 IAuthenticationService 进行的调用

public static class AuthenticationHttpContextExtensions
{

    public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) =>
        context.AuthenticateAsync(scheme: null);

    public static Task<AuthenticateResult> AuthenticateAsync(
      this HttpContext context, string? scheme) =>
        GetAuthenticationService(context).AuthenticateAsync(context, scheme);

    public static Task ChallengeAsync(this HttpContext context, string? scheme) =>
        context.ChallengeAsync(scheme, properties: null);


    public static Task ChallengeAsync(this HttpContext context) =>
        context.ChallengeAsync(scheme: null, properties: null);


    public static Task ChallengeAsync(
      this HttpContext context, AuthenticationProperties? properties) =>
        context.ChallengeAsync(scheme: null, properties: properties);


    public static Task ChallengeAsync(
      this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
        GetAuthenticationService(context).ChallengeAsync(context, scheme, properties);


    public static Task ForbidAsync(this HttpContext context, string? scheme) =>
        context.ForbidAsync(scheme, properties: null);

    public static Task ForbidAsync(this HttpContext context) =>
        context.ForbidAsync(scheme: null, properties: null);


    public static Task ForbidAsync(
      this HttpContext context, AuthenticationProperties? properties) =>
        context.ForbidAsync(scheme: null, properties: properties);

    public static Task ForbidAsync(
      this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
        GetAuthenticationService(context).ForbidAsync(context, scheme, properties);

    public static Task SignInAsync(
      this HttpContext context, string? scheme, ClaimsPrincipal principal) =>
        context.SignInAsync(scheme, principal, properties: null);


    public static Task SignInAsync(
      this HttpContext context, ClaimsPrincipal principal) =>
        context.SignInAsync(scheme: null, principal: principal, properties: null);


    public static Task SignInAsync(
      this HttpContext context, ClaimsPrincipal principal,
      AuthenticationProperties? properties) =>
        context.SignInAsync(scheme: null, principal: principal, properties: properties);


    public static Task SignInAsync(
      this HttpContext context, string? scheme,
      ClaimsPrincipal principal, AuthenticationProperties? properties) =>
      GetAuthenticationService(context).SignInAsync(context, scheme, principal, properties);

    public static Task SignOutAsync(
      this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);


    public static Task SignOutAsync(
      this HttpContext context, AuthenticationProperties? properties) =>
       context.SignOutAsync(scheme: null, properties: properties);


    public static Task SignOutAsync(
      this HttpContext context, string? scheme) =>
      context.SignOutAsync(scheme, properties: null);


    public static Task SignOutAsync(this HttpContext context, string? scheme,
    AuthenticationProperties? properties) =>
        GetAuthenticationService(context).SignOutAsync(context, scheme, properties);


    public static Task<string?> GetTokenAsync(this HttpContext context, string? scheme,
     string tokenName) =>
        GetAuthenticationService(context).GetTokenAsync(context, scheme, tokenName);


    public static Task<string?> GetTokenAsync(this HttpContext context, string tokenName)
     =>GetAuthenticationService(context).GetTokenAsync(context, tokenName);

    // This project doesn't reference
    // AuthenticationServiceCollectionExtensions.AddAuthentication so we use a string.
    private static IAuthenticationService GetAuthenticationService(HttpContext context) =>
        context.RequestServices.GetService<IAuthenticationService>() ??
            throw new InvalidOperationException(
              Resources.FormatException_UnableToFindServices(
                nameof(IAuthenticationService),
                nameof(IServiceCollection),
                "AddAuthentication"));
}

app.UseAuthentication() 内部使用的是 AuthenticationMiddleware 中间件,中间件会执行所有的 scheme, 同时会调用默认的 scheme ,使用 http context 上的扩展方法进行认证

public class AuthenticationMiddleware
{
    private readonly RequestDelegate _next;


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


    public IAuthenticationSchemeProvider Schemes { get; set; }

    public async Task Invoke(HttpContext context)
    {
        context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
        {
            OriginalPath = context.Request.Path,
            OriginalPathBase = context.Request.PathBase
        });

        // Give any IAuthenticationRequestHandler schemes a chance to handle the request
        var handlers = context.RequestServices.GetRequiredService<
        IAuthenticationHandlerProvider>();
        foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
        {
            var handler = await handlers.GetHandlerAsync(context, scheme.Name)
            as IAuthenticationRequestHandler;
            if (handler != null && await handler.HandleRequestAsync())
            {
                // http 请求不需要处理了直接返回
                return;
            }
        }

        var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
        if (defaultAuthenticate != null)
        {
            var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
            if (result?.Principal != null)
            {
                context.User = result.Principal;
            }
            if (result?.Succeeded ?? false)
            {
                var authFeatures = new AuthenticationFeatures(result);
                context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
                context.Features.Set<IAuthenticateResultFeature>(authFeatures);
            }
        }

        await _next(context);
    }
}

Authorization 授权

asp.net core 授权核心是基于 identity 的 claim 的执行一定的 police 策略

asp.net core 的授权使用的是策略模式,无论是角色或声明授权都是策略授权

IAuthorizationRequirement 授权需要的条件,这些条件的集合就是 police

// IAuthorizationRequirement 需要进行验证的参数写在这个接口的实现类里
// IAuthorizationHandler 会使用这些参数进行业务判断
public interface IAuthorizationRequirement
{
}

IAuthorizationHandler 授权处理器

//授权处理器接口 用户定义的 IAuthorizationHandler 需要注入容器
public interface IAuthorizationHandler
{
  Task HandleAsync(AuthorizationHandlerContext context);
}

//抽象授权处理器
public abstract class AuthorizationHandler<TRequirement>
: IAuthorizationHandler where TRequirement : IAuthorizationRequirement
{

  //处理所有的 IAuthorizationRequirement
  public virtual async Task HandleAsync(AuthorizationHandlerContext context)
  {
    foreach (TRequirement requirement in context.Requirements.OfType<TRequirement>())
      await this.HandleRequirementAsync(context, requirement).ConfigureAwait(false);
  }

  protected abstract Task HandleRequirementAsync(
    AuthorizationHandlerContext context,
    TRequirement requirement);
}

asp.net core 定义了很多的 IAuthorizationRequirement 实现类

public class ClaimsAuthorizationRequirement :
AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{

    public ClaimsAuthorizationRequirement(string claimType,
    IEnumerable<string>? allowedValues)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        ClaimType = claimType;
        AllowedValues = allowedValues;
    }

    public string ClaimType { get; }


    public IEnumerable<string>? AllowedValues { get; }


    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
     ClaimsAuthorizationRequirement requirement)
    {
        if (context.User != null)
        {
            var found = false;
            if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
            {
                found = context.User.Claims.Any(c => string.Equals(c.Type,
                requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
            }
            else
            {
                found = context.User.Claims.Any(c => string.Equals(
                  c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
                && requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
            }
            if (found)
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }

    public override string ToString()
    {
        var value = (AllowedValues == null || !AllowedValues.Any())
            ? string.Empty
            : $" and Claim.Value is one of the following values:
            ({string.Join("|", AllowedValues)})";

        return $"{nameof(ClaimsAuthorizationRequirement)}:Claim.Type={ClaimType}{value}";
    }
}

AuthorizationPolicyBuilder 定义了很多内置的方法直接增加策略

public class AuthorizationPolicyBuilder
{

    public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
    {
        AddAuthenticationSchemes(authenticationSchemes);
    }


    public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
    {
        Combine(policy);
    }


    public IList<IAuthorizationRequirement> Requirements { get; set; } =
    new List<IAuthorizationRequirement>();

    public IList<string> AuthenticationSchemes { get; set; } = new List<string>();

    public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
    {
        foreach (var authType in schemes)
        {
            AuthenticationSchemes.Add(authType);
        }
        return this;
    }


    public AuthorizationPolicyBuilder AddRequirements(
      params IAuthorizationRequirement[] requirements)
    {
        foreach (var req in requirements)
        {
            Requirements.Add(req);
        }
        return this;
    }

    public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
    {
        if (policy == null)
        {
            throw new ArgumentNullException(nameof(policy));
        }

        AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
        AddRequirements(policy.Requirements.ToArray());
        return this;
    }


    public AuthorizationPolicyBuilder RequireClaim(
      string claimType, params string[] allowedValues)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        return RequireClaim(claimType, (IEnumerable<string>)allowedValues);
    }


    public AuthorizationPolicyBuilder RequireClaim(
      string claimType, IEnumerable<string> allowedValues)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
        return this;
    }


    public AuthorizationPolicyBuilder RequireClaim(string claimType)
    {
        if (claimType == null)
        {
            throw new ArgumentNullException(nameof(claimType));
        }

        Requirements.Add(new ClaimsAuthorizationRequirement(
          claimType, allowedValues: null));
        return this;
    }


    public AuthorizationPolicyBuilder RequireRole(params string[] roles)
    {
        if (roles == null)
        {
            throw new ArgumentNullException(nameof(roles));
        }

        return RequireRole((IEnumerable<string>)roles);
    }

    public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)
    {
        if (roles == null)
        {
            throw new ArgumentNullException(nameof(roles));
        }

        Requirements.Add(new RolesAuthorizationRequirement(roles));
        return this;
    }

    public AuthorizationPolicyBuilder RequireUserName(string userName)
    {
        if (userName == null)
        {
            throw new ArgumentNullException(nameof(userName));
        }

        Requirements.Add(new NameAuthorizationRequirement(userName));
        return this;
    }

    public AuthorizationPolicyBuilder RequireAuthenticatedUser()
    {
        Requirements.Add(new DenyAnonymousAuthorizationRequirement());
        return this;
    }

    public AuthorizationPolicyBuilder RequireAssertion(
      Func<AuthorizationHandlerContext, bool> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Requirements.Add(new AssertionRequirement(handler));
        return this;
    }

    public AuthorizationPolicyBuilder RequireAssertion(
      Func<AuthorizationHandlerContext, Task<bool>> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        Requirements.Add(new AssertionRequirement(handler));
        return this;
    }


    public AuthorizationPolicy Build()
    {
        return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
    }
}

AuthorizationPolicyBuilder 构建的策略最后会生成 AuthorizationPolicy 对象

public class AuthorizationPolicy
{
    public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement>
     requirements, IEnumerable<string> authenticationSchemes)
    {
        if (requirements == null)
        {
            throw new ArgumentNullException(nameof(requirements));
        }

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

        if (!requirements.Any())
        {
            throw new InvalidOperationException(
              Resources.Exception_AuthorizationPolicyEmpty);
        }
        Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
        AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
    }


    public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }

    public IReadOnlyList<string> AuthenticationSchemes { get; }


    public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
    {
        if (policies == null)
        {
            throw new ArgumentNullException(nameof(policies));
        }

        return Combine((IEnumerable<AuthorizationPolicy>)policies);
    }
}

策略名及策略集合保存在 AuthorizationOptions 配置类里供 IAuthorizationService 授权服务调用

public class AuthorizationOptions
{
    private Dictionary<string, AuthorizationPolicy> PolicyMap { get; } =
    new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);

    public bool InvokeHandlersAfterFailure { get; set; } = true;

    public AuthorizationPolicy DefaultPolicy { get; set; } =
     new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();


    public AuthorizationPolicy? FallbackPolicy { get; set; }


    public void AddPolicy(string name, AuthorizationPolicy policy)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }

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

        PolicyMap[name] = policy;
    }

  >
    public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }

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

        var policyBuilder = new AuthorizationPolicyBuilder();
        configurePolicy(policyBuilder);
        PolicyMap[name] = policyBuilder.Build();
    }


    public AuthorizationPolicy? GetPolicy(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }

        if (PolicyMap.TryGetValue(name, out var value))
        {
            return value;
        }

        return null;
    }
}

一个 IAuthorizationHandler 处理多个 IAuthorizationRequirement

一个 IAuthorizationHandler 实现类 ,一个类可以处理多个 IAuthorizationRequirement 一个 police 可以有多个 IAuthorizationRequirement

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}
builder.Services.AddAuthorization(options => {
  options.AddPolicy("myPolice",
    options=>options.AddRequirements(new pendingRequirements(),
    new EditPermission()
    ));
});

IAuthorizationService 授权服务

public interface IAuthorizationService
{
    Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement>requirements);
    Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user, object? resource, string policyName);
}
public class DefaultAuthorizationService : IAuthorizationService
{
    private readonly AuthorizationOptions _options;
    private readonly IAuthorizationHandlerContextFactory _contextFactory;
    private readonly IAuthorizationHandlerProvider _handlers;
    private readonly IAuthorizationEvaluator _evaluator;
    private readonly IAuthorizationPolicyProvider _policyProvider;
    private readonly ILogger _logger;


    public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider,
    IAuthorizationHandlerProvider handlers, ILogger<DefaultAuthorizationService> logger,
     IAuthorizationHandlerContextFactory contextFactory,
     IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
        if (policyProvider == null)
        {
            throw new ArgumentNullException(nameof(policyProvider));
        }
        if (handlers == null)
        {
            throw new ArgumentNullException(nameof(handlers));
        }
        if (logger == null)
        {
            throw new ArgumentNullException(nameof(logger));
        }
        if (contextFactory == null)
        {
            throw new ArgumentNullException(nameof(contextFactory));
        }
        if (evaluator == null)
        {
            throw new ArgumentNullException(nameof(evaluator));
        }

        _options = options.Value;
        _handlers = handlers;
        _policyProvider = policyProvider;
        _logger = logger;
        _evaluator = evaluator;
        _contextFactory = contextFactory;
    }

    public virtual async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
     object? resource, IEnumerable<IAuthorizationRequirement> requirements)
    {
        if (requirements == null)
        {
            throw new ArgumentNullException(nameof(requirements));
        }

        var authContext = _contextFactory.CreateContext(requirements, user, resource);
        var handlers = await _handlers.GetHandlersAsync(authContext).
        ConfigureAwait(false);
        foreach (var handler in handlers)
        {
            await handler.HandleAsync(authContext).ConfigureAwait(false);
            if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
            {
                break;
            }
        }

        var result = _evaluator.Evaluate(authContext);
        if (result.Succeeded)
        {
            _logger.UserAuthorizationSucceeded();
        }
        else
        {
            _logger.UserAuthorizationFailed(result.Failure!);
        }
        return result;
    }


    public virtual async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
    object? resource, string policyName)
    {
      if (policyName == null)
      {
          throw new ArgumentNullException(nameof(policyName));
      }

      var policy = await _policyProvider.GetPolicyAsync(policyName).ConfigureAwait(false);
      if (policy == null)
      {
          throw new InvalidOperationException($"No policy found: {policyName}.");
      }
      return await this.AuthorizeAsync(user, resource, policy).ConfigureAwait(false);
  }
}

AuthorizeAttribute

public interface IAuthorizeData
  {
    /// <summary>
    /// Gets or sets the policy name that determines access to the resource.
    /// </summary>
    string? Policy { get; set; }

    /// <summary>
    /// Gets or sets a comma delimited list of roles that are allowed to
    ///access the resource.
    /// </summary>
    string? Roles { get; set; }

    /// <summary>
    /// Gets or sets a comma delimited list of schemes from which user
    ///information is constructed.
    /// </summary>
    string? AuthenticationSchemes { get; set; }
  }
}

public class AuthorizeAttribute : Attribute, IAuthorizeData
{
    public AuthorizeAttribute() { }

    public AuthorizeAttribute(string policy)
    {
        Policy = policy;
    }

    public string? Policy { get; set; }

    public string? Roles { get; set; }

    //合并多种认证的 claim
    public string? AuthenticationSchemes { get; set; }
}

asp.net core AuthorizeAttribute 通过 此属性为什么就能实现认证和授权的拦截呢

IApplicationModelProvider 通过此接口及约定可以动态的给 controller , action 添加过滤器 https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/application-model?view=aspnetcore-7.0#conventions

ASP.NET Core Identity

http://ASP.NET Core Identity 是一套包含了 UI、用户信息,密码、角色、管理以及邮件确认等等功 能的脚手架,支持自定义以及各种外部登录,包括 Facebook、Google、Microsoft、Twitter 等登录方 式。但是它的界面时基于微软的 razor 的,和目前流程的前后端框架不匹配,更多时有学习的价值

👍🎉🎊