使用需要 session api 键的 .NET Minimal API 创建一个 API
create an API with .NET Minimal APIs that require session api key
这个视频非常棒,展示了如何使用 .net 6 创建 Minimal APIs:
https://www.youtube.com/watch?v=eRJFNGIsJEo
令人惊奇的是,它如何使用依赖注入来获取端点内所需的大部分内容。例如,如果我需要自定义 header 的值,我会这样:
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;
});
我什至可以使用此代码注册我自己的 classes:builder.Services.AddTransient<TheClassIWantToRegister>()
如果我注册我的自定义 classes,我将能够在每次需要时创建一个 class 的实例和端点 (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
});
我已经尝试创建自定义 class RequiresApiTokenKey
并将 class 注册为 builder.Services.AddTransient<RequiresApiTokenKey>()
以便我的 API 知道如何创建一个实例那 class 在需要时但是我怎样才能访问那个 class 中的当前 http 上下文? 我怎样才能避免不得不重复检查 header API-KEY header 在每个需要它的方法中是否有效?
根据我的评论对此进行了测试。
这会在每次请求时调用中间件中的 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;
}
}
这个视频非常棒,展示了如何使用 .net 6 创建 Minimal APIs:
https://www.youtube.com/watch?v=eRJFNGIsJEo
令人惊奇的是,它如何使用依赖注入来获取端点内所需的大部分内容。例如,如果我需要自定义 header 的值,我会这样:
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;
});
我什至可以使用此代码注册我自己的 classes:builder.Services.AddTransient<TheClassIWantToRegister>()
如果我注册我的自定义 classes,我将能够在每次需要时创建一个 class 的实例和端点 (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
});
我已经尝试创建自定义 class RequiresApiTokenKey
并将 class 注册为 builder.Services.AddTransient<RequiresApiTokenKey>()
以便我的 API 知道如何创建一个实例那 class 在需要时但是我怎样才能访问那个 class 中的当前 http 上下文? 我怎样才能避免不得不重复检查 header API-KEY header 在每个需要它的方法中是否有效?
根据我的评论对此进行了测试。
这会在每次请求时调用中间件中的 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;
}
}