Modo corretto per implementare i controlli di autorizzazione in ASP MVC

8

In ASP MVC abbiamo l'attributo Authorize per eseguire il controllo a livello di controller o a livello di metodo controller. Ma cosa succede se hai bisogno di controllare le autorizzazioni all'interno di un metodo di controllo, per esempio, lascia che tu faccia qualche aggiornamento o crei un'azione per inviare un post sul blog. Alcuni utenti con autorizzazioni revant possono allegare file o rendere il post del blog appiccicoso. Pertanto, quando si crea un nuovo post, è necessario eseguire tutti questi controlli aggiuntivi prima di salvare il modello. In Laravel esiste il concetto di abilità in cui è possibile eseguire controlli all'interno di un metodo di controllo per verificare se un utente ha la capacità di eseguire le azioni pertinenti. Allo stesso modo puoi usare queste abilità nelle viste per controllare quale elemento mostrare o nascondere - tutto questo viene fuori dalla scatola.

C'è qualcosa di simile in ASP MVC. Come implementereste il controllo delle autorizzazioni all'interno di un metodo di controllo. Crei una classe di autorizzazione con proprietà come

public class Permissions
{
    private readonly IPrincipal user;

    public Permissions (IPrincipal user)
    {
        this.user = user;
    }

    public bool CanUploadFiles
    {
        get { return user.IsInAnyRole("Standard", "Admin"); }
    }

    public bool CanDeleteItems
    {
        get { return user.IsInRole("Admin"); }
    }

     public bool CanLockPost
    {
        get { return user.IsInRole("Admin"); }
    }

    // other permissions
}

Quindi all'interno dell'azione del controller:

 public ActionResult Create(PostViewModel viewModel)
 {
       var permissions = new Permissions(User);

        if (ModelState.IsValid)
        {
                var post = new Post
                {
                   if (permissions.CanLockPost)
                    {
                        post.IsLocked = viewModel.IsLocked;
                    }
                    if (permissions.CanStickyPost)
                    {
                        post.IsSticky = viewModel.IsSticky;
                    }
                    // Set other properties
                }

               _postRepository.Add(post);
        }   
  }

O vorresti salvare le autorizzazioni nel database. Mi piacerebbe sentire le tue opinioni su come implementare i controlli a un livello più granulare rispetto a un semplice livello di azione di controller o controller. Alcuni esempi di codice da dimostrare sarebbero utili.

    
posta adam78 22.07.2016 - 19:01
fonte

4 risposte

1

Dai un'occhiata a questo: link

"Il modo in cui questi ruoli sono creati e gestiti dipende dal backing store del processo di autorizzazione. I ruoli sono esposti allo sviluppatore tramite la proprietà IsInRole sulla classe ClaimsPrincipal."

Quindi, se stai utilizzando IsInRole () del principal per controllare i ruoli, penso che tu sia sulla strada giusta.

Alcune altre considerazioni casuali per te - Per semplificare l'utilizzo dei controlli delle autorizzazioni, se sono specifici per un modello di visualizzazione, è possibile esporli nella classe del modello di visualizzazione. In questo modo la vista del rasoio può controllare il modello della vista per vedere cosa dovrebbe / non dovrebbe mostrare all'utente.

Potresti anche utilizzare quegli stessi controlli delle autorizzazioni per generare errori di validazione implementando IValidatableObject nella classe del modello view e inserendo i tuoi controlli personalizzati nel metodo Validate. Quindi puoi gestire gli errori di autorizzazione che impediscono di salvare come errori di convalida.

    
risposta data 27.10.2016 - 19:28
fonte
1

Stiamo implementando questo metodo usando i metodi di estensione all'interfaccia IPrincipal:

public static class PermissionExtenions
{
    public bool CanUploadFiles(this IPrincipal user)
    {
        return user.IsInAnyRole("Standard", "Admin");
    }

    public bool CanDeleteItems(this IPrincipal user)
    {
        return user.IsInRole("Admin");
    }
}

Il bello di questo è che non è necessario creare un nuovo oggetto che avvolga IPrincipal . Se aggiungi una direttiva using nella parte superiore dei controller e la aggiungi agli spazi dei nomi nel file Views / Web.config, puoi utilizzarla in controller e visualizzazioni accedendo alla proprietà User .

Puoi anche usarlo nei tuoi attributi Autorizza personalizzati:

public void OnAuthorization(AuthorizationContext filterContext)
{
    var controller = filterContext.Controller as Controller;

    if (controller == null)
        return;

    var user = controller.User;

    if (!user.CanUploadFiles())
    {
        // redirect to some page
    }
    
risposta data 25.01.2017 - 22:28
fonte
0

Puoi imitare la struttura di AuthorizeAttribute. In uno dei miei progetti precedenti, ho separato le funzionalità "autorizzabili" in diversi metodi e aggiunto un attributo di autorizzazione personalizzato a questi metodi. Quindi, se hai bisogno di autorizzare una sotto funzione dei tuoi metodi di controllo, metti questa funzionalità nel suo metodo.

È possibile passare dati statici al costruttore dell'attributo per indicare un identificatore dell'oggetto autorizzabile e il proprio attributo può fare sempre la logica di autorizzazione necessaria sull'oggetto autorizzabile, tenendo conto del responsabile della sicurezza dell'utente richiedente / identità del servizio e azione di sicurezza.

Come autorizzi qualcosa è una storia completamente diversa. Consiglierei di selezionare un modello di autorizzazione comprovato (come RBAC, ABAC, ReBAC, ecc.) E di vedere se esistono librerie open source preesistenti che implementano tali modelli. In genere, ti consigliamo di non presentare il tuo modello di autorizzazione.

    
risposta data 22.07.2016 - 21:36
fonte
0

Ho usato attributi personalizzati in combinazione con il pattern CQRS e decoratore. Parlare è economico quindi ti darò solo il codice. Nota che l'attributo può essere usato a livello di controller OPPURE a livello di comando, o entrambi se sei veramente paranoico.

Nota: questo non è tutto il mio codice, il creatore di SimpleInjector (Steven van Deursen) l'ha ispirato in un post che non trovo più. Tutto è cablato tramite IoC (SimpleInjector).

/// <summary>
/// For the Command Side
/// </summary>
[Permission(Permission.StickyPost)]
public class StickyPostCommand
{
    public int PostId { get; set; }
}

public class StickyPostCommandHandler : ICommandHandler<StickyPostCommand>
{
    public void Handle(StickyPostCommand command)
    {
        //We only handle what we are here to handle (stickying a post), not checking permissions
    }
}

/// <summary>
/// This decorates all commands, and does some checks before passing it on to the real command (decoartee)
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public class PermissionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly MessagePermissionChecker<TCommand> permissionChecker;
    private readonly ICommandHandler<TCommand> decoratee;

    public PermissionCommandHandlerDecorator(MessagePermissionChecker<TCommand> permissionChecker, ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
        this.permissionChecker = permissionChecker;
    }
    public void Handle(TCommand command)
    {
        this.permissionChecker.CheckPermissionForCurrentUser();
        this.decoratee.Handle(command);
    }
}

/// <summary>
/// Pulls the custom attribute out of the command, delegate to the UserPermissionChecker if it has an attribute
/// </summary>
/// <typeparam name="TMessage"></typeparam>
public class MessagePermissionChecker<TMessage>
{
    private static readonly Guid? permissionId;
    private readonly IUserPermissionChecker permissionChecker;

    static MessagePermissionChecker()
    {
        var permissionAttribute = typeof(TMessage).GetCustomAttribute<PermissionAttribute>();
        if (permissionAttribute != null)
        {
            permissionId = Guid.Parse(permissionAttribute.PermissionId);
        }
    }

    public MessagePermissionChecker(IUserPermissionChecker permissionChecker)
    {
        this.permissionChecker = permissionChecker;
    }

    public void CheckPermissionForCurrentUser()
    {
        if (permissionId.HasValue)
        {
            this.permissionChecker.CheckPermission(permissionId);
        }
    }
}

public interface IUserPermissionChecker
{
    void CheckPermission(Guid id);
}

public class UserPermissionChecker : IUserPermissionChecker
{
    public void CheckPermission(Guid id)
    {
        //Go out to the database, check a role, whatever
        throw new SecurityException("Negative ghostrider, you can't do that!");
    }
}

/// <summary>
/// For the Controller Side
/// </summary>
public class PermissionActionFilter : IActionFilter<PermissionAttribute>
{
    private readonly IUserPermissionChecker _permissionChecker;

    public PermissionActionFilter(IUserPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    public void OnActionExecuting(PermissionAttribute attribute, ActionExecutingContext context)
    {
        this._permissionChecker.CheckPermission(Guid.Parse(attribute.PermissionId));
    }
}
    
risposta data 24.02.2017 - 23:53
fonte