我有几个使用 Bearer 身份验证的 Asp.Net Core Web APIIdentityServer4.AccessTokenValidation
用于内省令牌、验证用户身份并创建声明的中间件。这对于 HTTP 请求来说效果很好。
我正在将这些 API 配置为使用 RabbitMQ 作为传输的 MassTransit 端点(用于发布和消费消息)。我按照说明进行操作here https://masstransit-project.com/usage/configuration.html#asp-net-core用于将 MassTransit 添加到 API 以及设置消息使用者。典型的工作流程类似于:
对 API 的 HTTP 请求 > 在 MassTransit 上发布消息 > RabbitMQ > 在另一个 API 中使用的消息
我很难理解的是如何创建一个ClaimsPrincipal
当从总线上消费消息时,以便我知道代表哪个用户执行操作?如果不是 HTTP 请求,则不会调用 AuthenticationHandler。
到目前为止我尝试过的:
我想我可以通过在消息头中传递令牌(和/或单独的声明值)来解决这个问题。发布部分看起来很容易,就像 MassTransit 一样允许添加任意数量的自定义标头 https://masstransit-project.com/usage/producers.html#headers发布消息时使用MassTransit.PublishContextExecuteExtensions.Publish
。这使我能够将包含标识用户的信息的消息发送到传输上,并且可以通过手动查看标头在消费者中查看此信息,例如
public class SomeEventConsumer : IConsumer<SomeEventData>
{
public async Task Consume(ConsumeContext<SomeEventData> context)
{
var token = context.Headers["token"];
}
}
此时,我可以获取令牌并手动调用 Identity Server 中的内省端点,但随后我需要:
- 每次对每个消费者都这样做......
- ...将该信息手动传递给逻辑类等,而不是使用
IHttpContextAccessor.HttpContext.User.Claims
或者通过包装声明并使用依赖注入。
为了解决第 1 点,我创建了一个新的自定义中间件 https://masstransit-project.com/advanced/middleware/custom.html ...
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 请求,我不确定这里的最佳实践是什么。任何建议或指示将不胜感激。谢谢...