如何为.NET Core创建ApiKey身份验证方案

Mig*_*Slv 2 c# authentication .net-core asp.net-core-webapi

我正在尝试将 ApiKey 身份验证方案设置为我的 API,但找不到任何有关它的帖子或文档。

我能找到的更接近的东西是这个 Microsoft 页面,但没有提及如何注册身份验证处理程序。

我正在使用.Net Core 6。

Mig*_*Slv 11

找到了一个解决方案,主要基于这个伟大的 Joonas Westlin指南来实现基本身份验证方案。功劳应该归于他。

脚步:

  1. 实现继承自的选项类AuthenticationSchemeOptions以及之后需要的其他锅炉类。
  2. 创建处理程序,继承自AuthenticationHandler<TOptions>
  3. 重写处理程序方法HandleAuthenticateAsync以获取密钥并调用您的实现IApiKeyAuthenticationService
  4. AddScheme<TOptions, THandler>(string, Action<TOptions>)在 上注册方案,您可以通过调用服务集合AuthenticationBuilder获得该方案AddAuthentication
  5. 实施IApiKeyAuthenticationService并将其添加到服务集合中。

这里是所有代码。和AuthenticationSchemeOptions其他锅炉类别:

//the Service interface for the service that will get the key to validate against some store
public interface IApiKeyAuthenticationService
{
    Task<bool> IsValidAsync(string apiKey);
}

//the class for defaults following the similar to .Net Core JwtBearerDefaults class
public static class ApiKeyAuthenticationDefaults
{
    public const string AuthenticationScheme = "ApiKey";
}

public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions{}; //Nothing to do

public class ApiKeyAuthenticationPostConfigureOptions : IPostConfigureOptions<ApiKeyAuthenticationOptions>
{
    public void PostConfigure(string name, ApiKeyAuthenticationOptions options){} //Nothing to do
};
Run Code Online (Sandbox Code Playgroud)

处理程序:

public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
    private const string AuthorizationHeaderName = "Authorization";
    private const string ApiKeySchemeName = ApiKeyAuthenticationDefaults.AuthenticationScheme;
    private readonly IApiKeyAuthenticationService _authenticationService;

    public ApiKeyAuthenticationHandler(
        IOptionsMonitor<ApiKeyAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock,
        IApiKeyAuthenticationService authenticationService)
        : base(options, logger, encoder, clock)
    {
        _authenticationService = authenticationService;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey(AuthorizationHeaderName))
        {
            //Authorization header not in request
            return AuthenticateResult.NoResult();
        }

        if (!AuthenticationHeaderValue.TryParse(Request.Headers[AuthorizationHeaderName], out AuthenticationHeaderValue? headerValue))
        {
            //Invalid Authorization header
            return AuthenticateResult.NoResult();
        }

        if (!ApiKeySchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
        {
            //Not ApiKey authentication header
            return AuthenticateResult.NoResult();
        }
        if ( headerValue.Parameter is null)
        {
            //Missing key
            return AuthenticateResult.Fail("Missing apiKey");
        }            

        bool isValid = await _authenticationService.IsValidAsync(headerValue.Parameter);

        if (!isValid)
        {
            return AuthenticateResult.Fail("Invalid apiKey");
        }
        var claims = new[] { new Claim(ClaimTypes.Name, "Service") };            
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }

    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.Headers["WWW-Authenticate"] = $"ApiKey \", charset=\"UTF-8\"";
        await base.HandleChallengeAsync(properties);
    }
}
Run Code Online (Sandbox Code Playgroud)

对该类的扩展AuthenticationBuilder可以轻松注册 ApiKey 方案:

public static class ApiKeyAuthenticationExtensions
{
    public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder)
        where TAuthService : class, IApiKeyAuthenticationService
    {
        return AddApiKey<TAuthService>(builder, ApiKeyAuthenticationDefaults.AuthenticationScheme, _ => { });
    }

    public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, string authenticationScheme)
        where TAuthService : class, IApiKeyAuthenticationService
    {
        return AddApiKey<TAuthService>(builder, authenticationScheme, _ => { });
    }

    public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, Action<ApiKeyAuthenticationOptions> configureOptions)
        where TAuthService : class, IApiKeyAuthenticationService
    {
        return AddApiKey<TAuthService>(builder, ApiKeyAuthenticationDefaults.AuthenticationScheme, configureOptions);
    }

    public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, string authenticationScheme, Action<ApiKeyAuthenticationOptions> configureOptions)
        where TAuthService : class, IApiKeyAuthenticationService
    {
        builder.Services.AddSingleton<IPostConfigureOptions<ApiKeyAuthenticationOptions>, ApiKeyAuthenticationPostConfigureOptions>();
        builder.Services.AddTransient<IApiKeyAuthenticationService, TAuthService>();

        return builder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
            authenticationScheme, configureOptions);
    }
}
Run Code Online (Sandbox Code Playgroud)

实现身份验证服务以根据您的配置文件或其他存储来验证请求标头中的密钥:

public class ApiKeyAuthenticationService : IApiKeyAuthenticationService
{
    public Task<bool> IsValidAsync(string apiKey)
    {
        //Write your validation code here
        return Task.FromResult(apiKey == "Test");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,要使用它,只需在开头添加以下代码:

//register the schema
builder.Services
    .AddAuthentication(ApiKeyAuthenticationDefaults.AuthenticationScheme)
    .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, null);

//Register the Authentication service Handler that will be consumed by the handler.
builder.Services
    .AddSingleton<IApiKeyAuthenticationService,ApiKeyAuthenticationService>();
Run Code Online (Sandbox Code Playgroud)

或者,以更优雅的方式,使用扩展:

builder.Services
    .AddAuthentication(ApiKeyAuthenticationDefaults.AuthenticationScheme)
    .AddApiKey<ApiKeyAuthenticationService>();
Run Code Online (Sandbox Code Playgroud)