Как аутентифицировать пользователя при использовании сообщений MassTransit в Asp. Net Core Web API? - PullRequest
0 голосов
/ 04 февраля 2020

У меня есть несколько Asp. Net Core Web API, которые используют аутентификацию Bearer и IdentityServer4.AccessTokenValidation промежуточное ПО для анализа токенов, аутентификации пользователя и создания заявок. Это прекрасно работает для HTTP-запросов.

Я нахожусь в процессе настройки этих API-интерфейсов, чтобы они также были конечными точками MassTransit (как для публикации, так и для использования сообщений) с использованием RabbitMQ в качестве транспорта. Я следовал инструкциям здесь для добавления MassTransit к API и для настройки потребителей сообщений. Типичный рабочий процесс будет выглядеть примерно так:

HTTP-запрос к API> Publi sh сообщение на MassTransit> RabbitMQ> Сообщение используется в другом API

What I'm Изо всех сил пытаюсь понять, как я могу создать ClaimsPrincipal при потреблении сообщений с шины, чтобы я знал, для какого пользователя выполнять действия от имени? Там, где это не HTTP-запрос, не вызывается AuthenticationHandler.

Что я пробовал до сих пор:

Я думал, что подойду к этому, передав токен (и / или отдельные значения претензий) в заголовках сообщений. Часть publi sh выглядела достаточно легко, так как MassTransit позволяет добавлять любое количество пользовательских заголовков при публикации сообщений с использованием MassTransit.PublishContextExecuteExtensions.Publish. Это позволило мне получать на транспорт сообщения с информацией, идентифицирующей пользователя, и эту информацию можно просмотреть у потребителя, просмотрев заголовки вручную, например

public class SomeEventConsumer : IConsumer<SomeEventData>
{
    public async Task Consume(ConsumeContext<SomeEventData> context)
    {
        var token = context.Headers["token"];
    }
} 

. В этот момент я мог взять токен и вызвать Introspection. конечная точка в Identity Server вручную, но тогда мне нужно:

  1. Каждый раз делать это с каждым потребителем, а затем ...
  2. ... передавать эту информацию в logi c классы и c вручную вместо использования IHttpContextAccessor.HttpContext.User.Claims или путем обобщения утверждений и использования Dependency Injection.

К точке 1 я создал новое пользовательское промежуточное ПО ...

public class AuthenticationFilter<T> : IFilter<ConsumeContext<T>> where T : class
{
    public void Probe(ProbeContext context)
    {
        var scope = context.CreateFilterScope("authenticationFilter");
    }

    public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
    {
        var token = context.Headers.Where(x => x.Key == "token").Select(x => x.Value.ToString()).Single();

        // TODO: Call token introspection

        await next.Send(context);
    }
}

public class AuthenticationFilterSpecification<T> : IPipeSpecification<ConsumeContext<T>> where T : class
{
    public void Apply(IPipeBuilder<ConsumeContext<T>> builder)
    {
        var filter = new AuthenticationFilter<T>();
        builder.AddFilter(filter);
    }

    public IEnumerable<ValidationResult> Validate()
    {
        return Enumerable.Empty<ValidationResult>();
    }
}

public class AuthenticationFilterConfigurationObserver : ConfigurationObserver, IMessageConfigurationObserver
{
    public AuthenticationFilterConfigurationObserver(IConsumePipeConfigurator receiveEndpointConfigurator) : base(receiveEndpointConfigurator)
    {
        Connect(this);
    }

    public void MessageConfigured<TMessage>(IConsumePipeConfigurator configurator)
        where TMessage : class
    {
        var specification = new AuthenticationFilterSpecification<TMessage>();
        configurator.AddPipeSpecification(specification);
    }
}

public static class AuthenticationExtensions
{
    public static void UseAuthenticationFilter(this IConsumePipeConfigurator configurator)
    {
        if (configurator == null)
        {
            throw new ArgumentNullException(nameof(configurator));
        }

        _ = new AuthenticationFilterConfigurationObserver(configurator);
    }
}

... и затем добавил это в конвейер ...

IBusControl CreateBus(IServiceProvider serviceProvider)
{
    return Bus.Factory.CreateUsingRabbitMq(cfg =>
    {
        cfg.Host("rabbitmq://localhost");
        cfg.UseAuthenticationFilter();
        // etc ...
    });
}

И вот где я застрял. Я не знаю, как аутентифицировать пользователя для объема запроса. Где это не HTTP-запрос, я не уверен, что здесь лучше. Любые предложения или указатели будут с благодарностью приняты. Спасибо ...

...