Mediatr 3.0 使用管道行为进行身份验证

Posted

技术标签:

【中文标题】Mediatr 3.0 使用管道行为进行身份验证【英文标题】:Mediatr 3.0 Using Pipeline behaviors for authentication 【发布时间】:2017-05-23 05:35:55 【问题描述】:

着眼于使用新的 Mediatr 3.0 特性管道行为进行身份验证/授权。

您通常会根据消息还是处理程序进行身份验证?我问的原因是我会在处理程序上进行身份验证(与 MVC 中的控制器相同),但行为似乎不了解处理程序,所以我不确定这是可能/合适的。

我可以为每条消息添加一个 IAuthorisationRequired 标记接口,但如果该消息是一个通知/事件并且有多个处理程序,那么也许一些应该运行,而其他一些则不应该运行。在执行实际工作的处理程序代码上检查身份验证确实感觉更好。

希望能够将 [Authorize] 属性放在处理程序上并使用行为来检查它(我目前正是这样做的,但使用基类而不是行为)。

public class AuthenticationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>

    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    
        //Can't access handler class here, so how do I know the action requires authentication/authorization?
        return next();
    


[Authorize]
public class ChangePasswordRequestHandler : IAsyncRequestHandler<ChangePassword, ReponseType>
   
    protected override async Task<ReponseType> Handle(AsyncRequestBase<ChangePassword> message)
    
        //change users password here
    

【问题讨论】:

总的来说,我对任何提议的解决方案都不满意,最终还是坚持使用我的 3.0 之前的解决方案。授权应与相关操作而非消息相关联。 【参考方案1】:

你说得对,RequestDelegateHandler&lt;TResponse&gt; 没有公开接下来要运行的处理程序,这是故意的。如果您考虑一下,MediatR 2.x 中的管道使用了装饰器,虽然装饰器可以访问被装饰者的实例,但我建议不要基于它进行身份验证。原因是,如果您需要您的授权装饰器来装饰处理程序的一个特定实例 - 用特定属性装饰的那个 - 然后它们是耦合的,这违背了装饰器的目的,您应该能够将它们放在每个处理程序的顶部其他独立。

这就是为什么我建议根据消息进行授权,至少在大多数情况下是这样。您可以有一个可扩展的设计,其中每条消息都与几个授权规则相关联,并且行为会评估所有这些规则。

public interface IAuthorizationRule<TRequest>

    Task Evaluate(TRequest message);


public class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>

    private readonly IAuthorizationRule<TRequest>[] _rules;

    public AuthorizationBehavior(IAuthorizationRule<TRequest>[] rules)
    
        _rules = rules;
    

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    
        // catch it or let it bubble up depending on your strategy
        await Task.WaitAll(_rules.Select(x => x.Evaluate(request)));

        return next();
    

对于您提到的特定情况,对于通知,某些处理程序可能会运行而其他处理程序不应该运行,您始终可以使用针对该特定消息的授权行为,并有选择地将它们应用于需要它们的处理程序。我想我的意思是,当你遇到这些特定场景时,你必须自己动手。

【讨论】:

不幸的是,我认为您可能是对的。尽管我不喜欢根据消息进行身份验证,但它可能是处理行为的唯一方法。现在我想我会坚持使用我的 3.0 之前的方法,即使用基类并寻找 [authorize] 标签。 对不起,我错过了奖励你全部赏金的窗口 =( IAuthorizationRule&lt;TRequest&gt;[] rules 究竟是如何注入到这个管道行为中的?【参考方案2】:

我对一个项目有相同的要求,并实现了一个特定的管道,我可以在其中为特定请求注入(如果需要)一个 AuthorisationHandler。这意味着我只需要为我创建的每个新命令添加一个新的 AuthorisationHandler,然后在请求处理实际命令之前调用它。

管道:

public class Pipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>

    private readonly IAuthorisationHandler<TRequest, TResponse>[] _authorisationHandlers;
    private readonly IAsyncRequestHandler<TRequest, TResponse> _inner;
    private readonly IPostRequestHandler<TRequest, TResponse>[] _postHandlers;

    public Pipeline(IAuthorisationHandler<TRequest, TResponse>[] authorisationHandlers, IAsyncRequestHandler<TRequest, TResponse> inner, IPostRequestHandler<TRequest, TResponse>[] postHandlers)
    
        _authorisationHandlers = authorisationHandlers;
        _inner = inner;
        _postHandlers = postHandlers;
    

    public async Task<TResponse> Handle(TRequest message)
    
        foreach (var authorisationHandler in _authorisationHandlers)
        
            var result = (ICommandResult)await authorisationHandler.Handle(message);

            if (result.IsFailure)
            
                return (TResponse)result;
            
        

        var response = await _inner.Handle(message);

        foreach (var postHandler in _postHandlers)
        
            postHandler.Handle(message, response);
        

        return response;
    

授权处理程序:

 public class DeleteTodoAuthorisationHandler : IAuthorisationHandler<DeleteTodoCommand, ICommandResult>

    private IMediator _mediator;
    private IAuthorizationService _authorisationService;
    private IHttpContextAccessor _httpContextAccessor;

    public DeleteTodoAuthorisationHandler(IMediator mediator, IAuthorizationService authorisationService, IHttpContextAccessor httpContextAccessor)
    
        _mediator = mediator;
        _authorisationService = authorisationService;
        _httpContextAccessor = httpContextAccessor;
    

    public async Task<ICommandResult> Handle(DeleteTodoCommand request)
    
        if (await _authorisationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, "DeleteTodo"))
        
            return new SuccessResult();
        

        var message = "You do not have permission to delete a todo";

        _mediator.Publish(new AuthorisationFailure(message));

        return new FailureResult(message);
    

我的 AuthorisationHandler 实现了 IAuthorisationHandler,如下所示:

    public interface IAuthorisationHandler<in TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>

    Task<TResponse> Handle(TRequest request);
  

然后它使用 DecorateAllWith(结构图的一部分)挂在一起

cfg.For(typeof(IAsyncRequestHandler<,>)).DecorateAllWith(typeof(Pipeline<,>));

不确定是否应该为 3.x 执行此操作,因为它现在有一个新的管道接口

IPipelineBehavior<TRequest, TResponse>

尚未使用它,但我认为它会简化实现,意味着您可以停止使用装饰器模式 DecorateAllWith。

【讨论】:

这很好,但我正在寻找一些不同之处 1) 更新为使用 mediatr 3.0 行为 2) 可以在多个处理程序上工作的通用身份验证以及可以转换身份验证的 MVC 之类的简单方法根据该处理程序的要求(例如[授权])【参考方案3】:

您可以像我使用 Fluent Validation 一样执行此操作。

我创建了以下行为:

namespace MediatR.Extensions.FluentValidation

    public class ValidationPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    
        private readonly IValidator<TRequest>[] _validators;

        public ValidationPipelineBehavior(IValidator<TRequest>[] validators)
        
            _validators = validators;
        

        public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
        
            var context = new ValidationContext(request);

            var failures =
                _validators.Select(v => v.Validate(context)).SelectMany(r => r.Errors).Where(f => f != null).ToList();

            if (failures.Any())
            
                throw new ValidationException(failures);
            

            return await next();
        
    

创建一个 AbstractValidator

 public classs SaveCommand: IRequest<int>
 
    public string FirstName  get; set; 

    public string Surname  get; set; 
 

   public class SaveCommandValidator : AbstractValidator<SaveCommand>
    
       public SaveCommandValidator()
       
          RuleFor(x => x.FirstName).Length(0, 200);
          RuleFor(x => x.Surname).NotEmpty().Length(0, 200);
       
    

因此您可以创建一个Authorization&lt;T&gt; 类,您可以在其中添加每个请求的自定义授权代码并注入AuthorizationPipelineBehavior&lt;TRequest, TResponse&gt; 类。

【讨论】:

我认为这与 Mickaël Derriey 给出的答案基本相同,我是否遗漏了一些不同之处? IValidator&lt;TRequest&gt;[] 是如何注入到行为中的? @Shoe 我认为验证器的这个示例 di 有一些不同之处 使用标准 .netcore di 我可以注入像“public ValidationPipelineBehavior(IEnumerable>验证器)”

以上是关于Mediatr 3.0 使用管道行为进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

向 MediatR 行为管道添加验证?

使用mediatR管道行为添加验证。

来自管道行为的 MediatR 流畅的验证响应

Mediatr:单元测试行为/验证

返回带有错误的响应,而不是在验证管道 mediatr 3 中抛出异常

在 MediatR 管道上使用多个 FluentValidators