ASP.NET 核心 - 简单 API 密钥验证
ASP.NET core - simple API key authentication
我正在尝试为控制器中的某些 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);
}));
});
我用这个装饰了一个 API:
[HttpDelete("{itemId:guid}")]
[Authorize(Policy = Auth.Constants.WebmasterPolicyName)]
public async Task<ActionResult> DeleteCatalogItemAsync(Guid itemId)
当我在请求中设置正确的 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)
但我不确定如何处理该消息。我只想 return 对客户端的 401 响应。
我们可以创建自定义 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);
}
}
}
更多细节,可以参考这篇博客
我正在尝试为控制器中的某些 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);
}));
});
我用这个装饰了一个 API:
[HttpDelete("{itemId:guid}")]
[Authorize(Policy = Auth.Constants.WebmasterPolicyName)]
public async Task<ActionResult> DeleteCatalogItemAsync(Guid itemId)
当我在请求中设置正确的 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)
但我不确定如何处理该消息。我只想 return 对客户端的 401 响应。
我们可以创建自定义 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);
}
}
}
更多细节,可以参考这篇博客