用.net最小API创建一个需要会话API密钥的API



这个视频非常棒,展示了如何使用。net 6创建最小的api:

https://www.youtube.com/watch?v=eRJFNGIsJEo

它是如何使用依赖注入来获得你在端点中需要的几乎所有东西的,这是令人惊讶的。例如,如果我需要自定义标题的值,我可以这样写:

app.MapGet("/get-custom-header", ([FromHeader(Name = "User-Agent")] string data) =>
{
return $"User again is: {data}";
});

我可以有另一个端点,我可以访问整个httpContext,像这样:

app.MapGet("/foo", (Microsoft.AspNetCore.Http.HttpContext c) =>
{
var path = c.Request.Path;
return path;
});

我甚至可以用下面的代码注册我自己的类:builder.Services.AddTransient<TheClassIWantToRegister>()

如果我注册我的自定义类,我将能够创建该类的实例,每次我需要它和端点(app.MapGet("...)


不管怎样,回到问题上来。当用户登录时,我发送给他这个:

{
"ApiKey": "1234",
"ExpirationDate": blabla bla
.....
}

用户必须发送1234令牌才能使用API。如何避免像这样重复我的代码:

app.MapGet("/getCustomers", ([FromHeader(Name = "API-KEY")] string apiToken) =>
{
// validate apiToken agains DB
if(validationPasses)
return Database.Customers.ToList();
else
// return unauthorized
});

我尝试创建自定义类RequiresApiTokenKey并将该类注册为builder.Services.AddTransient<RequiresApiTokenKey>(),以便我的API知道如何在需要时创建该类的实例,但我如何才能访问该类内的当前http上下文例如?我怎么能避免不得不重复必须检查头API-KEY头是否在需要它的每个方法中有效?

根据我的评论做了一个测试。

这将在每个请求中调用中间件中的Invoke方法,您可以在这里进行检查。

可能更好的方法是使用AuthenticationHandler。使用此方法意味着您可以将单个端点属性为完成API密钥检查,而不是所有传入请求

但是,我认为这仍然是有用的,中间件可以用于任何你想要执行的每个请求

使用中间件

Program.cs:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//our custom middleware extension to call UseMiddleware
app.UseAPIKeyCheckMiddleware();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapGet("/", () => "Hello World!");
app.Run();

APIKeyCheckMiddleware.cs

using Microsoft.Extensions.Primitives;
internal class APIKeyCheckMiddleware
{
private readonly RequestDelegate _next;
public APIKeyCheckMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{

//we could inject here our database context to do checks against the db
if (httpContext.Request.Headers.TryGetValue("API-KEY", out StringValues value))
{
//do the checks on key
var apikey = value;
}
else
{
//return 403
httpContext.Response.StatusCode = 403;
}

await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class APIKeyCheckMiddlewareExtensions
{
public static IApplicationBuilder UseAPIKeyCheckMiddleware(this IApplicationBuilder builder)
{

return builder.UseMiddleware<APIKeyCheckMiddleware>();
}
}

我使用了SmithMart的答案,但必须更改Invoke方法中的内容,并在构造函数中使用DI。这是我的版本:

internal class ApiKeyCheckMiddleware
{
public static string ApiKeyHeaderName = "X-ApiKey";
private readonly RequestDelegate _next;
private readonly ILogger<ApiKeyCheckMiddleware> _logger;
private readonly IApiKeyService _apiKeyService;
public ApiKeyCheckMiddleware(RequestDelegate next, ILogger<ApiKeyCheckMiddleware> logger, IApiKeyService apiKeyService)
{
_next = next;
_logger = logger;
_apiKeyService = apiKeyService;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var request = httpContext.Request;
var hasApiKeyHeader = request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyValue);
if (hasApiKeyHeader)
{
_logger.LogDebug("Found the header {ApiKeyHeader}. Starting API Key validation", ApiKeyHeaderName);
if (apiKeyValue.Count != 0 && !string.IsNullOrWhiteSpace(apiKeyValue))
{
if (Guid.TryParse(apiKeyValue, out Guid apiKey))
{
var allowed = await _apiKeyService.Validate(apiKey);
if (allowed)
{
_logger.LogDebug("Client successfully logged in with key {ApiKey}", apiKeyValue);
var apiKeyClaim = new Claim("ApiKey", apiKeyValue);
var allowedSiteIdsClaim = new Claim("SiteIds", string.Join(",", allowedSiteIds));
var principal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { apiKeyClaim, allowedSiteIdsClaim }, "ApiKey"));
httpContext.User = principal;
await _next(httpContext);
return;
}
}
_logger.LogWarning("Client with ApiKey {ApiKey} is not authorized", apiKeyValue);
}
else
{
_logger.LogWarning("{HeaderName} header found, but api key was null or empty", ApiKeyHeaderName);
}
}
else
{
_logger.LogWarning("No ApiKey header found.");
}
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
}
}

最新更新