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

时间:2017-01-09 01:46:12

标签: c# mediatr

使用新的Mediatr 3.0功能管道行为进行身份验证/授权。

您通常会根据消息或处理程序进行身份验证吗?我问的原因是我在处理程序上进行身份验证(与MVC中的控制器相同)但是行为似乎没有关于处理程序的知识,因此我不确定这是否可行/适合。

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

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

resources :posts do
    get 'toggle_approve',   :on => :member
  end

3 个答案:

答案 0 :(得分:6)

你是对的,RequestDelegateHandler<TResponse>没有公开接下来要运行的处理程序,这是故意的。如果你考虑一下,MediatR 2.x中的管道使用装饰器,而装饰者可以访问decoratee的实例,我建议不要基于它进行auth。原因是,如果您需要授权装饰器来装饰处理程序的一个特定实例 - 用特定属性修饰的那个 - 那么它们会相互耦合,这会破坏装饰器的目的,您应该能够将它们放在顶部彼此独立。

这就是为什么我建议基于消息的授权,至少在大多数情况下。您可以使用可扩展的设计将每条消息与多个授权规则相关联,并且行为将评估所有这些规则。

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

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

答案 1 :(得分:2)

您可以像我使用Fluent验证一样进行此操作。

我创建了以下行为:

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<T>类,您可以在其中为每个请求添加自定义授权代码并注入AuthorizationPipelineBehavior<TRequest, TResponse>类。

答案 2 :(得分:1)

我对项目有相同的要求并实现了一个特定的管道,我可以为特定的请求注入(如果需要)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。