web-dev-qa-db-fra.com

Remplacer le filtre d'autorisation global dans ASP.NET Core 1.0 MVC

J'essaie de configurer l'autorisation dans l'application Web ASP.NET Core 1.0 (MVC 6).

Approche plus restrictive - par défaut, je veux restreindre tous les contrôleurs et méthodes d'action aux utilisateurs avec le rôle Admin. Donc, j'ajoute un attribut global d'autorisation comme:

AuthorizationPolicy requireAdminRole = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .RequireRole("Admin")
    .Build();
services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(requireAdminRole));});

Ensuite, je veux autoriser les utilisateurs avec des rôles spécifiques à accéder à des contrôleurs concrets. Par exemple:

[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}

Ce qui bien sûr ne fonctionnera pas, car le "filtre global" ne permettra pas aux UserManager d'accéder au contrôleur car ils ne sont pas des "administrateurs".

Dans MVC5, j'ai pu l'implémenter en créant un attribut d'autorisation personnalisé et en y plaçant ma logique. Ensuite, en utilisant cet attribut personnalisé comme global. Par exemple:

public class IsAdminOrAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        ActionDescriptor action = filterContext.ActionDescriptor;
        if (action.IsDefined(typeof(AuthorizeAttribute), true) ||
            action.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
        {
            return;
        }

        base.OnAuthorization(filterContext);
    }
}

J'ai essayé de créer un AuthorizeFilter personnalisé, mais sans succès. L'API semble être différente.

Ma question est donc la suivante: est-il possible de configurer une stratégie par défaut, puis de la remplacer pour des contrôleurs et des actions spécifiques. Ou quelque chose de similaire. Je ne veux pas aller avec ça

[Authorize(Roles="Admin,[OtherRoles]")]

sur chaque contrôleur/action, car il s'agit d'un problème de sécurité potentiel. Que se passera-t-il si j'oublie accidentellement de mettre le rôle Admin.

25
regnauld

Vous devrez jouer un peu avec le cadre car votre stratégie globale est plus restrictive que celle que vous souhaitez appliquer à des contrôleurs et des actions spécifiques:

  • Par défaut seulement Admin les utilisateurs peuvent accéder à votre application
  • Des rôles spécifiques auront également accès à certains contrôleurs (comme UserManagers accédant à UsersController)

Comme vous l'avez déjà remarqué, avoir un filtre global signifie que seuls les utilisateurs Admin auront accès à un contrôleur. Lorsque vous ajoutez l'attribut supplémentaire sur le UsersController, seuls les utilisateurs qui sont les deux Admin et UserManager y aura accès.

Il est possible d'utiliser une approche similaire à celle de MVC 5, mais cela fonctionne d'une manière différente.

  • Dans MVC 6, le [Authorize] l'attribut ne contient pas la logique d'autorisation.
  • Au lieu de cela, la AuthorizeFilter est celle qui a une méthode OnAuthorizeAsync appelant le service d'autorisation pour s'assurer que les stratégies sont satisfaites.
  • Un IApplicationModelProvider spécifique est utilisé pour ajouter un AuthorizeFilter pour chaque contrôleur et action qui a un [Authorize] attribut.

Une option pourrait être de recréer votre IsAdminOrAuthorizeAttribute, mais cette fois en tant que AuthorizeFilter que vous ajouterez ensuite en tant que filtre global:

public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context)
    {
        // If there is another authorize filter, do nothing
        if (context.Filters.Any(item => item is IAsyncAuthorizationFilter && item != this))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});

Cela appliquerait votre filtre global uniquement lorsque le contrôleur/l'action n'a pas de [Authorize] attribut.


Vous pouvez également éviter d'avoir un filtre global en vous injectant dans le processus qui génère les filtres à appliquer pour chaque contrôleur et action. Vous pouvez soit ajouter votre propre IApplicationModelProvider ou votre propre IApplicationModelConvention. Les deux vous permettront d'ajouter/supprimer des contrôleurs spécifiques et des filtres d'actions.

Par exemple, vous pouvez définir une stratégie d'autorisation par défaut et des stratégies spécifiques supplémentaires:

services.AddAuthorization(opts =>
{
    opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
    opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});

Ensuite, vous pouvez créer un nouveau IApplicatioModelProvider qui ajoutera la stratégie par défaut à chaque contrôleur qui n'a pas son propre [Authorize] attribut (une convention d'application serait très similaire et probablement plus alignée avec la façon dont le cadre est destiné à être étendu. J'ai simplement utilisé rapidement le AuthorizationApplicationModelProvider existant comme guide):

public class OverridableDefaultAuthorizationApplicationModelProvider : IApplicationModelProvider
{
    private readonly AuthorizationOptions _authorizationOptions;

    public OverridableDefaultAuthorizationApplicationModelProvider(IOptions<AuthorizationOptions> authorizationOptionsAccessor)
    {
        _authorizationOptions = authorizationOptionsAccessor.Value;
    }

    public int Order
    {
        //It will be executed after AuthorizationApplicationModelProvider, which has order -990
        get { return 0; }
    }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            if (controllerModel.Filters.OfType<IAsyncAuthorizationFilter>().FirstOrDefault() == null)
            {
                //default policy only used when there is no authorize filter in the controller
                controllerModel.Filters.Add(new AuthorizeFilter(_authorizationOptions.DefaultPolicy));
            }
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {            
        //empty    
    }
}

//Register in Startup.ConfigureServices
services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApplicationModelProvider, OverridableDefaultAuthorizationApplicationModelProvider>());

Avec cela en place, la stratégie par défaut sera utilisée sur ces 2 contrôleurs:

public class FooController : Controller

[Authorize]
public class BarController : Controller

Et la politique spécifique des utilisateurs sera utilisée ici:

[Authorize(Policy = "Users")]
public class UsersController : Controller

Notez que vous devez toujours ajouter le rôle d'administrateur à chaque stratégie, mais au moins toutes vos stratégies seront déclarées dans une seule méthode de démarrage. Vous pouvez probablement créer vos propres méthodes de création de stratégies qui ajouteront toujours le rôle d'administrateur.

35
Daniel J.G.

En utilisant la solution de @ Daniel, j'ai rencontré le même problème mentionné par @TarkaDaal dans le commentaire (il y a 2 AuthorizeFilter dans le contexte pour chaque appel ... je ne sais pas trop d'où ils viennent).

Donc, ma façon de le résoudre est la suivante:

public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
    {
        if (context.Filters.Any(f =>
        {
            var filter = f as AuthorizeFilter;
            //There's 2 default Authorize filter in the context for some reason...so we need to filter out the empty ones
            return filter?.AuthorizeData != null && filter.AuthorizeData.Any() && f != this;
        }))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});

C'est moche mais cela fonctionne dans ce cas parce que si vous n'utilisez que l'attribut Authorize sans argument, vous serez de toute façon géré par le filtre new AuthorizationPolicyBuilder().RequireRole("admin").Build().

1
cheesemacfly