2017-01-09 15 views
9

Przeglądanie nowych zachowań potoku funkcji Mediatr 3.0 w celu uwierzytelnienia/autoryzacji.Mediatr 3.0 Używanie zachowania potoków do uwierzytelniania

Czy normalnie autoryzowałbyś się na podstawie wiadomości lub obsługi? powodem, dla którego pytam, jest to, żebym był autoryzowany w programie obsługi (tak samo jak kontroler w MVC), ale zachowania nie wydają się mieć wiedzy o obsłudze, więc nie jestem pewien, czy jest to możliwe/odpowiednie.

Mogę dodać interfejs znacznika IAuthorisationRequired do każdej wiadomości, ale jeśli wiadomość jest powiadomieniem/zdarzeniem i ma wiele procedur obsługi, to może niektóre powinny zostać uruchomione, ale inne nie. Naprawdę czuję się lepiej sprawdzając auth na kodzie obsługi, który wykonuje rzeczywistą pracę.

Chciałbym móc umieścić atrybut [Autoryzuj] na funkcji obsługi i użytkownika, aby to sprawdzić (obecnie robię to dokładnie, ale z klasą bazową zamiast zachowania).

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 
    } 
} 
+0

Ogólnie rzecz biorąc, nie byłem zadowolony z żadnego z proponowanych rozwiązań i w końcu trzymałem się mojego wstępnego rozwiązania 3.0. Autoryzacja powinna być powiązana z daną czynnością, a nie z komunikatem. – Betty

Odpowiedz

4

Jesteś rację, RequestDelegateHandler<TResponse> nie narażać co handler będzie trwał dalej, a to jest zamierzone. Jeśli się nad tym zastanowić, potoki użyte w dekoratorach MediatR 2.x, i chociaż dekorator miał dostęp do instancji dekoratora, odradzałbym wykonywanie na podstawie tego auth. Powodem jest to, że jeśli potrzebujesz dekoratora autoryzacji do dekoracji konkretnego przypadku obsługi - tego ozdobionego specyficznymi atrybutami - to są one połączone, co pokonuje cel dekoratorów, w których powinieneś być w stanie umieścić je na szczycie każdego z nich. inne niezależnie.

Dlatego radziłbym, aby w większości przypadków opierać autoryzację na wiadomości. Możesz mieć rozszerzalny projekt, w którym do każdej wiadomości są powiązane różne reguły autoryzacji, a zachowanie ocenia je wszystkie.

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

W szczególnym przypadku można wymienić, gdy na zgłoszenia, niektóre koparki może działać, podczas gdy inni nie powinien, zawsze można użyć zachowań zezwoleń że cel, który konkretny komunikat i stosuje je wybiórczo do ładowarki, które ich potrzebują . Sądzę, że chodzi mi o to, że będziesz musiał sam się trochę przygotować, kiedy trafisz na te konkretne scenariusze.

+0

Niestety, myślę, że możesz mieć rację. Chociaż nie lubię robić auth na podstawie wiadomości, może to być jedyny sposób na zachowanie. Na razie myślę, że będę trzymać się mojej metody pre 3.0 za pomocą klasy bazowej i szukanie tagów [autoryzuj]. – Betty

+0

Przepraszam, ominąłem okno, aby przyznać ci pełną nagrodę = ( – Betty

+0

haha ​​nie martw się, cieszę się, że znalazłeś odpowiedź pomocną! –

1

Miałem taki sam wymóg dla projektu i wdrożono konkretny rurociąg, w którym mogłem wstrzyknąć (jeśli jest to wymagane) AuthorisingHandler dla konkretnego żądania. Oznacza to, że wystarczy dodać nowy AuthorisationHandler dla każdego nowego polecenia, które utworzyłem, a następnie zostanie wywołany przed żądaniem przetworzenia faktycznego polecenia.

Rurociąg:

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

Authorsiation Handler:

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

My AuthorisationHandler implemements IAuthorisationHandler który wygląda następująco:

public interface IAuthorisationHandler<in TRequest, TResponse> where TRequest : IAsyncRequest<TResponse> 
{ 
    Task<TResponse> Handle(TRequest request); 
} 

Następnie zawiesza razem używając DecorateAllWith (część struktury)

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

Nie jesteś pewien, że powinieneś zrobić to dla 3.x, jak to ma teraz nowy interfejs rurociągu

IPipelineBehavior<TRequest, TResponse> 

Nieużywany go jeszcze, ale myślę, że będzie to uproszczenie wdrażania i oznacza, że ​​można zatrzymać za pomocą wzoru dekorator DecorateAllWith.

+0

To jest ładne, ale ja " m szuka kilku różnic 1) zaktualizowano do korzystania z mediatr 3.0 behaviors 2) ogólne uwierzytelnianie, które może działać na wielu programach obsługi i niezły sposób, taki jak MVC, który może włączyć uwierzytelnianie zgodnie z wymaganiami dla tego handlerka (np. [Authorised]) – Betty

2

Możesz to zrobić w taki sam sposób, w jaki korzystam z Fluent Validation.

stworzyłem następujące zachowanie:

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

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

Więc można utworzyć klasę Authorization<T> gdzie można dodać swój własny kod autoryzacji za zamówienie i wstrzyknięcia do AuthorizationPipelineBehavior<TRequest, TResponse> klasa.

+0

Myślę, że to w zasadzie ta sama odpowiedź, którą dał Mickaël Derriey, czy brakuje mi jakiejś różnicy? – Betty

+0

W jaki sposób "IValidator []" jest wstrzykiwany w to zachowanie? – Shoe

Powiązane problemy