这个问题和其他问题对我一路走来帮助很大。就我而言,我总是错过了另一件事——在使用[Authorize] 装饰动作/控制器时,SwaggerUI 没有将我选择的标头名称/值 (X-API-KEY) 传递给我的身份验证处理程序。我的项目使用 .NET Core 3.1 和 Swashbuckle 5。我创建了一个继承 IOperationFilter 的自定义类,它使用下面的 Swashbuckle.AspNetCore.Filters nuget 包来搭载他们对 oauth2 的实现。
// Startup.cs
// ...
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = nameof(BoardMinutes), Version = "v1" });
// Adds authentication to the generated json which is also picked up by swagger.
options.AddSecurityDefinition(ApiKeyAuthenticationOptions.DefaultScheme, new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = ApiKeyAuthenticationHandler.ApiKeyHeaderName,
Type = SecuritySchemeType.ApiKey
});
options.OperationFilter<ApiKeyOperationFilter>();
});
关键组件是options.AddSecurityDefinition()(我有一些开放的端点,不想提供全局过滤器)以及options.OperationFilter<ApiKeyOperationFilter>()。
// ApiKeyOperationFilter.cs
// ...
internal class ApiKeyOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Piggy back off of SecurityRequirementsOperationFilter from Swashbuckle.AspNetCore.Filters which has oauth2 as the default security scheme.
var filter = new SecurityRequirementsOperationFilter(securitySchemaName: ApiKeyAuthenticationOptions.DefaultScheme);
filter.Apply(operation, context);
}
}
最后 - 对于完整的图片,这里是身份验证处理程序和身份验证选项
// ApiKeyAuthenticationOptions.cs
// ...
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "API Key";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
// ApiKeyAuthenticationHandler.cs
// ...
internal class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private const string ProblemDetailsContentType = "application/problem+json";
public const string ApiKeyHeaderName = "X-Api-Key";
private readonly IApiKeyService _apiKeyService;
private readonly ProblemDetailsFactory _problemDetailsFactory;
public ApiKeyAuthenticationHandler(
IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IApiKeyService apiKeyService,
ProblemDetailsFactory problemDetailsFactory) : base(options, logger, encoder, clock)
{
_apiKeyService = apiKeyService ?? throw new ArgumentNullException(nameof(apiKeyService));
_problemDetailsFactory = problemDetailsFactory ?? throw new ArgumentNullException(nameof(problemDetailsFactory));
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues))
{
return AuthenticateResult.NoResult();
}
Guid.TryParse(apiKeyHeaderValues.FirstOrDefault(), out var apiKey);
if (apiKeyHeaderValues.Count == 0 || apiKey == Guid.Empty)
{
return AuthenticateResult.NoResult();
}
var existingApiKey = await _apiKeyService.FindApiKeyAsync(apiKey);
if (existingApiKey == null)
{
return AuthenticateResult.Fail("Invalid API Key provided.");
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, existingApiKey.Owner)
};
var identity = new ClaimsIdentity(claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return AuthenticateResult.Success(ticket);
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
Response.ContentType = ProblemDetailsContentType;
var problemDetails = _problemDetailsFactory.CreateProblemDetails(Request.HttpContext, StatusCodes.Status401Unauthorized, nameof(HttpStatusCode.Unauthorized),
detail: "Bad API key.");
await Response.WriteAsync(JsonSerializer.Serialize(problemDetails));
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
Response.StatusCode = StatusCodes.Status403Forbidden;
Response.ContentType = ProblemDetailsContentType;
var problemDetails = _problemDetailsFactory.CreateProblemDetails(Request.HttpContext, StatusCodes.Status403Forbidden, nameof(HttpStatusCode.Forbidden),
detail: "This API Key cannot access this resource.");
await Response.WriteAsync(JsonSerializer.Serialize(problemDetails));
}
}