How to authenticate a user when consuming MassTransit messages in Asp.Net Core Web API?

匆匆过客 提交于 2021-01-21 05:07:12

问题


I have several Asp.Net Core Web APIs that use Bearer authentication and IdentityServer4.AccessTokenValidation middleware to introspect tokens, authenticate the user and create claims. This works fine for HTTP requests.

I am in the process of configuring these APIs to also be MassTransit endpoints (for both Publishing and Consuming messages) using RabbitMQ as transport. I followed the instructions here for adding MassTransit to the API and for setting up message consumers. A typical workflow will be something like:

HTTP Request to API > Publish message on MassTransit > RabbitMQ > Message consumed in another API

What I'm struggling to understand is how I can create a ClaimsPrincipal when consuming messages off the bus so that I know which user to perform actions on behalf of? Where it's not an HTTP request there is no AuthenticationHandler being invoked.

What I've tried so far:

I thought I'd approach this by passing a token (and/or individual claim values) in message headers. The publish part seemed easily enough as MassTransit allows adding any number of custom headers when publishing messages using MassTransit.PublishContextExecuteExtensions.Publish. This allowed me to get messages onto the transport with information identifying a user and this info can be viewed in a consumer by manually viewing the headers e.g.

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

At this point I could take the token and call the Introspection endpoint in Identity Server manually but then I'd need to:

  1. Do this in every consumer every time and then ...
  2. ... pass that information down to logic classes etc manually instead of making use of IHttpContextAccessor.HttpContext.User.Claims or by wrapping the claims and using Dependency Injection.

To address point 1 I created a new custom middleware ...

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);
    }
}

... and then added that into the pipeline ...

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

And this is where I'm stuck. I don't know how to authenticate the user for the scope of the request. Where it's not an HTTP request I'm not sure what best practice is here. Any suggestions or pointers would be gratefully received. Thanks...

来源:https://stackoverflow.com/questions/60064118/how-to-authenticate-a-user-when-consuming-masstransit-messages-in-asp-net-core-w

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!