天天看點

ASP.NET Core 實作自定義認證 #yyds幹貨盤點#

前言

在 ASP.NET Core 中,我們常使用基于 JWT 的認證:

services.AddAuthentication(option =>
{
    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = false,
        ValidateIssuerSigningKey = true,
        ValidIssuer = Configuration["JwtToken:Issuer"],
        ValidAudience = Configuration["JwtToken:Issuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtToken:SecretKey"]))
    };
});      

但有時候,我們需要使用自定義認證,比如使用QueryString(htttp://xxx?_key=xxx),隻要請求中包含的​

​_key​

​的值正确即可。

AddJwtBearer 實作原理

為了實作自定義認證,我們決定仿照​

​AddJwtBearer​

​的實作機制。

​AddJwtBearer​

​​實際執行的是​

​AddScheme​

​方法:

public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<JwtBearerOptions> configureOptions)
{
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
    return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, displayName, configureOptions);
}      

​JwtBearerHandler​

​​是具體的處理程式,繼承自​

​AuthenticationHandler<TOptions>​

​​,主要代碼在​

​HandleAuthenticateAsync​

​内:

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
  ...
 
  if (string.IsNullOrEmpty(token))
  {
      string authorization = Request.Headers.Authorization.ToString();

      // If no authorization header found, nothing to process further
      if (string.IsNullOrEmpty(authorization))
      {
          return AuthenticateResult.NoResult();
      }

      if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
      {
          token = authorization.Substring("Bearer ".Length).Trim();
      }

      // If no token found, no further work possible
      if (string.IsNullOrEmpty(token))
      {
          return AuthenticateResult.NoResult();
      }
  }

  ...

  foreach (var validator in Options.SecurityTokenValidators)
  {
      if (validator.CanReadToken(token))
      {
        ...

        var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
        {
            Principal = principal,
            SecurityToken = validatedToken
        };

        ...

        tokenValidatedContext.Success();
        return tokenValidatedContext.Result!;
      }
  }

  ...
}      

從​

​Request.Headers.Authorization​

​​擷取token,然後用​

​Options.SecurityTokenValidators​

​驗證token合法後,傳回結果。

Demo

​DemoAuthenticationOptions​

建立​

​DemoAuthenticationOptions​

​​,繼承自​

​AuthenticationSchemeOptions​

​:

public class DemoAuthenticationOptions : AuthenticationSchemeOptions
{
    public const string Scheme = "Demo";
}      

​DemoAuthenticationHandler​

建立​

​DemoAuthenticationHandler​

​​,繼承自​

​AuthenticationHandler<DemoAuthenticationOptions>​

​:

public class DemoAuthenticationHandler : AuthenticationHandler<DemoAuthenticationOptions>
{
    public DemoAuthenticationHandler(IOptionsMonitor<DemoAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
    : base(options, logger, encoder, clock)
    { }
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        throw new NotImplementedException();
    }
}      

​實作 HandleAuthenticateAsync 方法​

從請求的​

​Query​

​​中擷取​

​key​

​,然後檢查是否合法:

protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{
    if (!Request.Query.TryGetValue("_key", out var keys))
    {
        return AuthenticateResult.NoResult();
    }

    var key = keys.FirstOrDefault();

    //到資料庫檢索
    if (key =="123456")
    {
      var claims = new List<Claim>
      {
          new Claim(ClaimTypes.Name, "My IO")
      };

      var identity = new ClaimsIdentity(claims, DemoAuthenticationOptions.Scheme);
      var identities = new List<ClaimsIdentity> { identity };
      var principal = new ClaimsPrincipal(identities);
      var ticket = new AuthenticationTicket(principal, DemoAuthenticationOptions.Scheme);

      return AuthenticateResult.Success(ticket);
    }

    return AuthenticateResult.NoResult();
}      

​定義擴充方法​

定義擴充方法,使用我們上面建立的​

​DemoAuthenticationHandler​

​:

public static class AuthenticationBuilderExtensions
{
    public static AuthenticationBuilder AddDemoAuthentication(this AuthenticationBuilder authenticationBuilder, Action<DemoAuthenticationOptions> options)
    {
        return authenticationBuilder.AddScheme<DemoAuthenticationOptions, DemoAuthenticationHandler>(DemoAuthenticationOptions.Scheme, options);
    }
}      

​使用​

修改Startup.cs:

services.AddAuthentication(option =>
{
    option.DefaultAuthenticateScheme = DemoAuthenticationOptions.Scheme;
    option.DefaultChallengeScheme = DemoAuthenticationOptions.Scheme;

})
    .AddDemoAuthentication(options => { });      

結論

當不加​

​Query​

​​或使用錯誤的​

​key​

​時,傳回401 認證失敗:

ASP.NET Core 實作自定義認證 #yyds幹貨盤點#

僅當使用正确的​

​key​

​時,API 通路成功: