Скрытие конечных точек Swagger для конкретного пользователя

Вопросы и ответы

Я пытаюсь скрыть, какие конечные точки отображаются в Swagger в зависимости от того, какие пользователи вошли в систему. Это не работает, и я не понимаю, почему. Я использую Microsoft Identity и Swashbuckle. В классе HideEndpointsBasedOnUserFilter код попадает в “if (!isUserInRole)” и заходит в это условие, но команды внутри не удаляют конечную точку swagger. Вот мой код:

[SwaggerHide("Admin")]
[HttpGet]
[Route("cabinetGatewaySvc/accesscontrol/{clientId}/{cardId}")]
public async Task<ActionResult<string>> AccessControl(string clientId, string cardId)
{
    _twoTraceLogger.LogTrace("Вызывается CabinetController");
    var result = await _canOpenPort.ValidateWearerAsync(clientId, cardId);
    return Ok(result);
}

namespace MsCabinetGateway.SwaggerConfig
{
    public class SwaggerHideAttribute : Attribute
    {
        public string RequiredRole { get; set; }

        public SwaggerHideAttribute(string requiredRole)
        {
            RequiredRole = requiredRole;
        }
    }
}

public class HideEndpointsBasedOnUserFilter : IOperationFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HideEndpointsBasedOnUserFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var user = _httpContextAccessor.HttpContext?.User;

        if (user == null || !user.Identity.IsAuthenticated)
        {
            return; // Нет аутентифицированного пользователя, пропустить фильтрацию
        }

        // Проверка, есть ли у действия атрибут SwaggerHideAttribute
        var hideAttribute = context.MethodInfo.GetCustomAttribute<SwaggerHideAttribute>();
        if (hideAttribute != null)
        {
            var requiredRole = hideAttribute.RequiredRole;
            var isUserInRole = user.IsInRole(requiredRole);

            // Запись ролей и требуемой роли в журнал
            Console.WriteLine($"Требуемая роль: {requiredRole}, Пользователь в роли: {isUserInRole}");

            // Если у пользователя нет необходимой роли, скрываем конечную точку
            if (!isUserInRole)
            {
                operation.Tags.Clear(); // Скрыть операцию, удалив теги
                operation.Summary = null; // Опционально очистить аннотацию
                operation.Description = null; // Опционально очистить описание
                operation.Responses.Clear(); // Опционально очистить ответы
            }
        }
    }
}

public class Program
{
    private static AppSettings? ApplicationSettings { get; set; }
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        var connectionString = 
        builder.Configuration.GetConnectionString("MsCabinetGatewayContextConnection") ?? 
        throw 
        new InvalidOperationException("Строка подключения 'MsCabinetGatewayContextConnection' не найдена.");

        builder.Services.AddDbContext<MsCabinetGatewayContext>(options => 
        options.UseSqlServer(connectionString));
        builder.Services.AddHttpContextAccessor();
        builder.Services.AddDefaultIdentity<Areas.Identity.Data.MsCabinetGatewayUser> 
        (options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<MsCabinetGatewayContext>();
        IConfiguration configuration = builder.Configuration;
        ApplicationSettings = new AppSettings();
        configuration.Bind(ApplicationSettings);

        builder.Services.AddControllers();
        // Узнайте больше о настройке Swagger/OpenAPI на 
        https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen(c => {
            c.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "Cabinet gateway",
                Version = "v1",
                Description = "Документация по кабинету."
            });

            c.OperationFilter<HideEndpointsBasedOnUserFilter>();

            // Установите путь к комментариям для Swagger JSON и UI.
            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            c.IncludeXmlComments(xmlPath);

            c.EnableAnnotations();
            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
            {
                Name = "Authorization",
                Type = SecuritySchemeType.ApiKey,
                Scheme = "Bearer",
                BearerFormat = "JWT",
                In = ParameterLocation.Header,
                Description = 
                Adapters.RestApiAdapter.Constants.RestProperties.Description,
            });
            c.AddSecurityRequirement(new OpenApiSecurityRequirement {
                {
                    new OpenApiSecurityScheme {
                        Reference = new OpenApiReference {
                            Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                        }
                    },
                    new string[] {}
                }
            });
            builder.Services.AddHttpContextAccessor();
        });

        builder.Services.AddCustomConfiguration(configuration);
        if (ApplicationSettings.AppInsightConfig is null)
        {
            ApplicationSettings.AppInsightConfig = new();
            ApplicationSettings.AppInsightConfig.InstrumentationKey =
            configuration.GetSection("ApplicationInsights").GetSection("InstrumentationKey").Value;
            ApplicationSettings.AppInsightConfig.ConnectionString =
            configuration.GetSection("ApplicationInsights").GetSection("ConnectionString").Value;
        }
        builder.Services.AddServiceDependencies(ApplicationSettings);

        //builder.WebHost.ConfigureKestrel((context, serverOptions) =>
        //{
        //    serverOptions.Listen(IPAddress.Any, 5000);
        //});

        builder.Services.AddLogging(builder =>
        {
            // Только Application Insights зарегистрирован как поставщик журнала
            builder.AddApplicationInsights(
                configureTelemetryConfiguration:
                (config) => config.ConnectionString = 
            configuration["ApplicationInsights:ConnectionString"],
                configureApplicationInsightsLoggerOptions: (options) => { }
            );
        });

        var app = builder.Build();

        GrpcAdapterExtention.AddServiceConfigurations(app);
        //app.Use(async (context, next) =>
        //{
        //    if (context.Request.Path.StartsWithSegments("/swagger")
        //        && !context.User.Identity.IsAuthenticated)
        //    {
        //        context.Response.Redirect("/Identity/Account/Login");
        //        return;
        //    }
        //    await next();
        //});
        app.UseAuthentication();
        app.UseAuthorization();
        app.MapRazorPages();
        app.UseStaticFiles();
        app.UseSwagger();
        app.UseSwaggerUI();
        app.UseSwagger(c =>
        {
            c.RouteTemplate = "cabinetGatewaySvc/swagger/{documentname}/swagger.json";
        });

        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/cabinetGatewaySvc/swagger/v1/swagger.json", "Cabinet Gateway Microservice");
            c.RoutePrefix = "cabinetGatewaySvc/swagger";
        });

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            _ = endpoints.MapControllers();
        });
        app.Run();
    }
}

Ответ

Давайте разберем вашу проблему и попробуем устранить недостатки в коде для скрытия конечных точек в Swagger в зависимости от ролей пользователей.

Проблема

Ваша логика в HideEndpointsBasedOnUserFilter выглядит правильно на первый взгляд, но вы упоминаете, что команды внутри if (!isUserInRole) не удаляют конечные точки из Swagger. Возможно, проблема заключается в том, что вы не полностью скрываете конечную точку, а только очищаете её теги и другие метаданные. Это не обязательно приведет к тому, что конечная точка не будет отображаться в Swagger UI.

Решение

Вместо очистки информации о конечных точках, лучше полностью исключить эти конечные точки из Swagger. Для этого можно использовать context.ApiDescription в вашем OperationFilter. Вот как это можно сделать:

  1. Измените класс HideEndpointsBasedOnUserFilter:
public class HideEndpointsBasedOnUserFilter : IOperationFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HideEndpointsBasedOnUserFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var user = _httpContextAccessor.HttpContext?.User;

        if (user == null || !user.Identity.IsAuthenticated)
        {
            return; // Не аутентифицированный пользователь
        }

        var hideAttribute = context.MethodInfo.GetCustomAttribute<SwaggerHideAttribute>();
        if (hideAttribute != null)
        {
            var requiredRole = hideAttribute.RequiredRole;
            var isUserInRole = user.IsInRole(requiredRole);

            // Логирование ролей и требуемой роли
            Console.WriteLine($"Требуемая роль: {requiredRole}, Имеет ли пользователь эту роль: {isUserInRole}");

            // Если у пользователя нет необходимой роли, исключаем конечную точку
            if (!isUserInRole)
            {
                // Исходя из контекста, исключаем конечную точку из Swagger
                context.ApiDescription.HttpMethod = "DELETE"; // Удаление метода
                context.ApiDescription.RelativePath = ""; // Удаление функционала
            }
        }
    }
}
  1. Убедитесь, что вы правильно добавили SwaggerHideAttribute к вашим методам:
[SwaggerHide("Admin")]
[HttpGet]
[Route("cabinetGatewaySvc/accesscontrol/{clientId}/{cardId}")]
public async Task<ActionResult<string>> AccessControl(string clientId, string cardId)
{
    // Ваш код здесь
}

Проверка конфигурации Swagger

Кроме того, убедитесь, что вы правильным образом настроили Swagger в методе ConfigureServices:

builder.Services.AddSwaggerGen(c =>
{
    // Ваша настройка Swagger
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "Cabinet gateway",
        Version = "v1",
        Description = "Cabinet gateway documentation."
    });

    // Добавьте OperationFilter для фильтрации конечных точек
    c.OperationFilter<HideEndpointsBasedOnUserFilter>();

    // Другие настройки
});

Заключение

Попробуйте внести вышеуказанные изменения в ваш класс HideEndpointsBasedOnUserFilter и проверьте, исчезают ли конечные точки из Swagger UI для пользователей, которые не имеют необходимых прав. Если конечные точки все равно будут отображаться, убедитесь, что в вашем проекте настроены все зависимости и компоненты правильно.

Оцените материал
Добавить комментарий

Капча загружается...