这个视频非常棒,展示了如何使用。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;
}
}