ASP.NET core - 简单的API密钥身份验证

sil*_*ent 16 c# asp.net-core .net-6.0

我正在尝试为控制器中的某些 API 构建一个超级简单的 API 密钥身份验证。为此,我有这个ConfigureServices()

services.AddAuthorization(options =>
{
  options.AddPolicy(
    Auth.Constants.WebmasterPolicyName,
    policy =>
      policy.RequireAssertion(context =>
      {
        if (context.Resource is HttpContext httpContext)
        {
          if (httpContext.Request.Headers.TryGetValue("X-API-KEY", out var header))
          {
            var val = header.FirstOrDefault()?.ToLower();
            if (val == "my-super-secret-key")
            {
              return Task.FromResult(true);
            }
          }
        }
        return Task.FromResult(false);
      })
  );
});
Run Code Online (Sandbox Code Playgroud)

我用这个装饰了一个 API:

[HttpDelete("{itemId:guid}")]
[Authorize(Policy = Auth.Constants.WebmasterPolicyName)]
public async Task<ActionResult> DeleteCatalogItemAsync(Guid itemId)
Run Code Online (Sandbox Code Playgroud)

当我在请求中设置正确的 API 密钥时,效果非常好。

问题是负面情况:当密钥丢失或错误时,我会收到 500 错误:

System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
   at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at AlwaysOn.CatalogService.Startup.<>c__DisplayClass5_0.<<Configure>b__3>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
Run Code Online (Sandbox Code Playgroud)

但我不知道该如何处理该消息。我只是希望它向客户端返回 401 响应。

小智 33

您需要实现自定义身份验证处理程序才能正确处理此问题。是一个关于如何做到这一点的很好的例子。

总结一下这个想法,您需要注册一个自定义身份验证方案:

builder.Services.AddAuthentication("ApiKey")
    .AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationSchemeHandler>(
        "ApiKey",
        opts => opts.ApiKey = configuration.GetValue<string>("api-key")
    );
Run Code Online (Sandbox Code Playgroud)

并为选项定义一个类:

public class ApiKeyAuthenticationSchemeOptions: AuthenticationSchemeOptions {
  public string ApiKey {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

并实现一个处理程序类ApiKeyAuthenticationSchemeHandler:AuthenticationHandler<ApiKeyAuthenticationSchemeOptions>。有方法

protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
   var apiKey = Context.Request.Headers["X-API-KEY"];
   if (apiKey != Options.ApiKey) {
       return Task.FromResult(AuthenticateResult.Fail("Invalid X-API-KEY"));
   }
   var claims = new[] { new Claim(ClaimTypes.Name, "VALID USER") };
   var identity = new ClaimsIdentity(claims, Scheme.Name);
   var principal = new ClaimsPrincipal(identity);
   var ticket = new AuthenticationTicket(principal, Scheme.Name);
   return Task.FromResult(AuthenticateResult.Success(ticket));
} 
Run Code Online (Sandbox Code Playgroud)

最后,在控制器中,您可以添加授权属性,如下所示:

[Authorize(AuthenticationSchemes = "ApiKey")]


Jas*_*Pan 15

我们可以创建一个自定义ApiKeyMiddleware来实现simple API key authentication

它在某种程度上与我们在自定义属性中所做的类似,但您会注意到这里的主要区别是我们不能直接设置上下文的 Response 对象,而是必须单独分配状态码和消息。

示例代码:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;

namespace SecuringWebApiUsingApiKey.Middleware
{
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string APIKEYNAME = "ApiKey";
    public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Api Key was not provided. (Using ApiKeyMiddleware) ");
            return;
        }

        var appSettings = context.RequestServices.GetRequiredService<IConfiguration>();

        var apiKey = appSettings.GetValue<string>(APIKEYNAME);

        if (!apiKey.Equals(extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Unauthorized client. (Using ApiKeyMiddleware)");
            return;
        }

        await _next(context);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

欲了解更多详情,我们可以参考这篇博客。

使用 API 密钥身份验证保护 ASP.NET Core Web API