天天看点

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 访问成功: