Я расскажу, как реализовать аутентификацию с использованием как JWT, так и API-ключа на одном и том же endpoint в ASP.NET Core Web API. Совмещение этих схем аутентификации полезно, если вы хотите использовать токен JWT Bearer для аутентификации пользователей и API-ключ для аутентификации между сервисами.

Эта статья основана на моем предыдущей статье, в которой я уже рассмотрел аутентификацию с использованием API-ключа в ASP.NET Core. Если вам нужно подробное объяснение этой реализации, обратитесь к ней. Эта реализация была протестирована как в .NET 8, так и в .NET 9.
ASP.NET Core позволяет комбинировать обработчики аутентификации для поддержки нескольких схем, и я продемонстрирую, как это реализовать.
Реализация составного обработчика аутентификации
Для того чтобы включить аутентификацию как с использованием JWT, так и с использованием API-ключа на одном endpoint, нам нужен составной обработчик аутентификации. Этот обработчик определяет, какую схему аутентификации применить в зависимости от запроса. Сначала мы проверим, содержит ли запрос API-ключ; если нет, мы применим схему аутентификации с использованием JWT. Реализация показана ниже:
internal static class CompositeAuthenticationDefaults { public const string AuthenticationScheme = $"Composite-{JwtBearerDefaults.AuthenticationScheme}-{ApiKeyAuthenticationDefaults.ApiKeyHeaderName}"; } internal sealed class CompositeAuthenticationHandler( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder) { protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var scheme = GetAuthunticationScheme(); return await Context.AuthenticateAsync(scheme); } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { var scheme = GetAuthunticationScheme(); await Context.ChallengeAsync(scheme); } protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) { var scheme = GetAuthunticationScheme(); await Context.ForbidAsync(scheme); } private bool IsApiKeyAuthScheme() => Request.Headers.ContainsKey(ApiKeyAuthenticationDefaults.ApiKeyHeaderName); private string GetAuthunticationScheme() => IsApiKeyAuthScheme() ? ApiKeyAuthenticationDefaults.AuthenticationScheme : JwtBearerDefaults.AuthenticationScheme; }
Настройка составной схемы аутентификации
Далее нам нужно настроить наши службы аутентификации для использования как JWT, так и API-ключа в файле Program.cs:
builder.Services.AddAuthentication(options => { options.DefaultScheme = CompositeAuthenticationDefaults.AuthenticationScheme; }) .AddScheme<AuthenticationSchemeOptions, CompositeAuthenticationHandler>(CompositeAuthenticationDefaults.AuthenticationScheme, _ => { }) .AddScheme<AuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, _ => { }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Authority = "authority"; options.MetadataAddress = "metadata-address"; // e.g. https://login.microsoftonline.com/your-tenant-id/v2.0/.well-known/openid-configuration options.RequireHttpsMetadata = !builder.Environment.IsDevelopment(); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true }; }); builder.Services.AddAuthorization();
В этом примере конфигурация JWT гарантирует, что токены выданы доверенным провайдером и проверяет ключевые аспекты безопасности, такие как срок действия и ключ подписи.
Чтобы добавить аутентификацию JWT в ваш проект, установите пакет Microsoft.AspNetCore.Authentication.JwtBearer из NuGet.
Применение схем аутентификации в контроллерах
Для защиты API с использованием обеих схем аутентификации примените атрибут [Authorize] следующим образом:
[Authorize] [ApiController] [Route("api/[controller]")] public class SecureController : ControllerBase { [HttpGet] public IActionResult GetSecureData() { return Ok(new { Message = "This is a secure endpoint." }); } }
Если вы хотите ограничить endpoint использованием только JWT схемы, укажите:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Аналогично для API Key:
[Authorize(AuthenticationSchemes = ApiKeyAuthenticationDefaults.AuthenticationScheme)]
Ниже пример использования [Authorize] атрибута в minimal API:
app.MapGet("/api/secure", [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] () => { return new { Message = "This is a secure endpoint." }; }).RequireAuthorization();
Заключение
Реализовав свой составной обработчик аутентификации, мы можем без проблем поддерживать как JWT, так и аутентификацию с использованием API-ключа на одном и том же API endpoint. Этот подход предоставляет гибкость для сценариев, когда различные пользователи вашего API требуют разных методов аутентификации.
Если вы нашли это руководство полезным или у вас есть вопросы, не стесняйтесь оставить комментарий!
Дополнение в ответ на комментарий: Использование AuthorizationPolicyBuilder для установки политики по умолчанию, как в коде ниже:
builder.Services.AddAuthorizationBuilder() .SetDefaultPolicy(new AuthorizationPolicyBuilder(ApiKeyAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build());
не подходит, если вы хотите применять только одну схему аутентификации для конкретного API-эндпоинта, поскольку устанавливает политику авторизации по умолчанию, которая допускает аутентификацию либо через API-ключ, либо через JWT для всех защищенных эндпоинтов. Это делает невозможным ограничение эндпоинта только одной схемой (например, только JWT или только API-ключ). Это может непреднамеренно привести к несанкционированному доступу, если эндпоинт должен поддерживать только один метод аутентификации. Например, если эндпоинт предназначен для аутентификации через JWT, но по умолчанию также разрешает API-ключ, это может создать угрозу безопасности, позволяя API-ключу обходить более строгие проверки аутентификации.
ссылка на оригинал статьи https://habr.com/ru/articles/879424/
Добавить комментарий