Mediatr 3.0 使用管道行为进行身份验证
Mediatr 3.0 Using Pipeline behaviors for authentication
查看 authentication/authorization 的新 Mediatr 3.0 功能管道行为。
您通常会根据消息或处理程序进行身份验证吗?我问的原因是我对处理程序进行了授权(与 MVC 中的控制器相同),但行为似乎不了解处理程序所以我不确定这是 possible/suitable.
我可以为每条消息添加一个 IAuthorisationRequired 标记接口,但是如果消息是 notification/event 并且有多个处理程序,那么也许有些应该 运行 而不是其他的。在执行实际工作的处理程序代码上检查身份验证确实感觉更好。
希望能够在处理程序上放置一个 [Authorize] 属性,并使用一个行为来检查它(我目前正是这样做的,但使用的是基础 class 而不是行为)。
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
}
}
你是对的,RequestDelegateHandler<TResponse>
没有暴露接下来 运行 的处理程序,这是故意的。如果您考虑一下,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();
}
}
对于您提到的特定情况,对于通知,某些处理程序可能 运行 而其他处理程序不应该,您始终可以使用针对该特定消息的授权行为,并将它们选择性地应用于处理程序需要他们。我想我的意思是,当您遇到这些特定场景时,您将不得不自己动手制作一些东西。
我对一个项目有相同的要求,并实施了一个特定的管道,我可以在其中为特定请求注入(如果需要)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。
您可以按照我使用 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();
}
}
}
创建一个抽象验证器
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>
class,您可以在其中为每个请求添加您的自定义授权代码并注入到 AuthorizationPipelineBehavior<TRequest, TResponse>
class。
查看 authentication/authorization 的新 Mediatr 3.0 功能管道行为。
您通常会根据消息或处理程序进行身份验证吗?我问的原因是我对处理程序进行了授权(与 MVC 中的控制器相同),但行为似乎不了解处理程序所以我不确定这是 possible/suitable.
我可以为每条消息添加一个 IAuthorisationRequired 标记接口,但是如果消息是 notification/event 并且有多个处理程序,那么也许有些应该 运行 而不是其他的。在执行实际工作的处理程序代码上检查身份验证确实感觉更好。
希望能够在处理程序上放置一个 [Authorize] 属性,并使用一个行为来检查它(我目前正是这样做的,但使用的是基础 class 而不是行为)。
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
}
}
你是对的,RequestDelegateHandler<TResponse>
没有暴露接下来 运行 的处理程序,这是故意的。如果您考虑一下,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();
}
}
对于您提到的特定情况,对于通知,某些处理程序可能 运行 而其他处理程序不应该,您始终可以使用针对该特定消息的授权行为,并将它们选择性地应用于处理程序需要他们。我想我的意思是,当您遇到这些特定场景时,您将不得不自己动手制作一些东西。
我对一个项目有相同的要求,并实施了一个特定的管道,我可以在其中为特定请求注入(如果需要)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。
您可以按照我使用 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();
}
}
}
创建一个抽象验证器
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>
class,您可以在其中为每个请求添加您的自定义授权代码并注入到 AuthorizationPipelineBehavior<TRequest, TResponse>
class。