ASP.NET Core 实现自定义认证

前言

在 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 实现自定义认证

仅当使用正确的key时,API 访问成功:

ASP.NET Core 实现自定义认证