前言
在 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 furtherif (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 possibleif (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 认证失败:

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

想了解更多内容,请关注我的个人公众号”My IO“