问题描述
我有几个ASP.NET Core WebAPI,它们使用持有者身份验证和IdentityServer4.AccessTokenValidation
中间件来自省令牌、身份验证用户和创建声明。这适用于HTTP请求。
我正在使用RabbitMQ作为传输将这些API配置为MassTransfer端点(用于发布和消费消息)。我按照here的说明将MassTransport添加到API并设置消息使用者。典型的工作流程如下:
对API的HTTP请求>在MassTransport上发布消息>RabbitMQ>另一个API使用的消息
我很难理解的是,当从总线上消费消息时,如何创建ClaimsPrincipal
,以便我知道代表哪个用户执行操作?如果它不是HTTP请求,则没有调用任何AuthenticationHandler。
我目前尝试的内容:
我想我应该通过在消息头中传递一个令牌(和/或单个声明值)来实现这一点。使用MassTransit.PublishContextExecuteExtensions.Publish
发布邮件时,发布部件看起来很容易作为MassTransferallows adding any number of custom headers。这使我能够使用标识用户的信息将邮件放到传输上,并且可以通过手动查看标头(例如
public class SomeEventConsumer : IConsumer<SomeEventData>
{
public async Task Consume(ConsumeContext<SomeEventData> context)
{
var token = context.Headers["token"];
}
}
此时,我可以获取令牌并手动调用Identity Server中的自检端点,但随后需要:
- 每次在每个用户中执行此操作,然后.
- ..。手动将该信息向下传递到逻辑类等,而不是使用
IHttpContextAccessor.HttpContext.User.Claims
或包装声明并使用依赖项注入。
为了寻址点%1,我创建了一个新的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);
}
}
..。然后将其添加到管道中.
IBusControl CreateBus(IServiceProvider serviceProvider)
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host("rabbitmq://localhost");
cfg.UseAuthenticationFilter();
// etc ...
});
}
这就是我被困的地方。我不知道如何针对请求范围对用户进行身份验证。如果它不是HTTP请求,我不确定这里的最佳实践是什么。如有任何建议或建议,我们将不胜感激。谢谢.
Pluralsight
我刚刚看了一个关于推荐答案的Kevin Dockx课程,它涵盖了Azure服务总线上的这个场景,但是同样的原则也适用于公共交通或使用消息总线的服务之间的任何其他异步通信。以下是指向该部分的链接:Securing Microservices in ASP.NET Core
Kevin的技术是将访问令牌(JWT)作为属性包含在总线消息中,然后使用IdentityModel
在使用者中进行验证。
总结:
在生产者中:
- 从请求中获取访问令牌(例如
HttpContext.GetUserAccessTokenAsync()
)。 - 发送前将其设置为邮件中的属性。
在消费者中:
- 使用
IdentityModel
获取IdP发现文档 - 从发现响应中提取公共签名密钥(这些密钥必须转换为
RsaSecurityKey
) - 调用
JwtSecurityTokenHandler.ValidateToken()
验证消息中的JWT。如果成功,则返回ClaimsPrincipal
。
如果您担心访问令牌过期,您可以利用消息入队的日期时间作为使用者中令牌验证逻辑的一部分。
以下是验证器的工作方式(简化):
var discoveryDocumentResponse = await httpClient.GetDiscoveryDocumentAsync("https://my.authority.com");
var issuerSigningKeys = new List<SecurityKey>();
foreach (var webKey in discoveryDocumentResponse.KeySet.Keys)
{
var e = Base64Url.Decode(webKey.E);
var n = Base64Url.Decode(webKey.N);
var key = new RsaSecurityKey(new RSAParameters
{ Exponent = e, Modulus = n })
{
KeyId = webKey.Kid
};
issuerSigningKeys.Add(key);
}
var tokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = "my-api-audience",
ValidIssuer = "https://my.authority.com",
IssuerSigningKeys = issuerSigningKeys
};
var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(tokenToValidate,
tokenValidationParameters, out var rawValidatedToken);
return claimsPrincipal;
这篇关于在ASP.NET Core Web API中使用批量传输消息时如何对用户进行身份验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!