Добавил получение guid пользователя по фио через токен
Create and publish a Docker image / Publish image (push) Successful in 3m54s

This commit is contained in:
2025-09-08 16:52:14 +03:00
parent dbfcaac425
commit 82e7d92584
12 changed files with 164 additions and 12 deletions
@@ -0,0 +1,7 @@
namespace SfeduSchedule.Auth;
public static class ApiKeyAuthenticationDefaults
{
public const string Scheme = "ApiKey";
public const string HeaderName = "X-Api-Key";
}
@@ -0,0 +1,72 @@
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
namespace SfeduSchedule.Auth;
public class ApiKeyAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IConfiguration configuration)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Ожидаем ключ из ENV
var expectedKey = configuration["API_KEY"];
if (string.IsNullOrEmpty(expectedKey))
return Task.FromResult(AuthenticateResult.Fail("API key is not configured."));
// Ищем ключ в заголовке X-Api-Key (и опционально в query ?api_key=)
string? providedKey = null;
if (Request.Headers.TryGetValue(ApiKeyAuthenticationDefaults.HeaderName, out var values))
providedKey = values.FirstOrDefault();
if (string.IsNullOrEmpty(providedKey) &&
Request.Query.TryGetValue("api_key", out var qv))
providedKey = qv.FirstOrDefault();
if (string.IsNullOrEmpty(providedKey))
// Нет ключа — позволь другим схемам (если есть) продолжить; при [Authorize] будет 401
return Task.FromResult(AuthenticateResult.NoResult());
if (!ConstantTimeEquals(providedKey!, expectedKey))
return Task.FromResult(AuthenticateResult.Fail("Invalid API key."));
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "api-key"),
new Claim(ClaimTypes.Name, "api-key-user"),
};
var identity = new ClaimsIdentity(claims, ApiKeyAuthenticationDefaults.Scheme);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, ApiKeyAuthenticationDefaults.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.Headers["WWW-Authenticate"] =
$"{ApiKeyAuthenticationDefaults.Scheme} realm=\"api\", header=\"{ApiKeyAuthenticationDefaults.HeaderName}\"";
Response.StatusCode = 401;
return Task.CompletedTask;
}
private static bool ConstantTimeEquals(string a, string b)
{
var aBytes = Encoding.UTF8.GetBytes(a);
var bBytes = Encoding.UTF8.GetBytes(b);
if (aBytes.Length != bBytes.Length)
return false;
return CryptographicOperations.FixedTimeEquals(aBytes, bBytes);
}
}
@@ -0,0 +1,33 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Authorization;
namespace SfeduSchedule.Auth
{
public class SwaggerAuthorizeOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize = context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.DeclaringType?.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() == true;
if (hasAuthorize)
{
operation.Security ??= new List<OpenApiSecurityRequirement>();
operation.Security.Add(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = ApiKeyAuthenticationDefaults.Scheme
}
},
new List<string>()
}
});
}
}
}
}