wake-up-neo.com

Überschreiben Sie den globalen Autorisierungsfilter in ASP.NET Core 1.0 MVC

Ich versuche, die Autorisierung in der ASP.NET Core 1.0-Webanwendung (MVC 6) einzurichten.

Einschränkenderer Ansatz - Standardmäßig möchte ich alle Controller und Aktionsmethoden auf Benutzer mit der Rolle Admin beschränken. Daher füge ich ein globales Authorize-Attribut wie das folgende hinzu:

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

Dann möchte ich Benutzern mit bestimmten Rollen den Zugriff auf konkrete Steuerungen ermöglichen. Beispielsweise:

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

Was natürlich nicht funktioniert, da der "globale Filter" dem UserManager nicht erlaubt, auf den Controller zuzugreifen, da es sich nicht um "Administratoren" handelt.

In MVC5 konnte ich dies implementieren, indem ich ein benutzerdefiniertes Berechtigungsattribut erstellte und meine Logik dort platzierte. Verwenden Sie dann dieses benutzerdefinierte Attribut als globales Attribut. Beispielsweise:

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

Ich habe versucht, ein benutzerdefiniertes AuthorizeFilter zu erstellen, aber kein Erfolg. API scheint anders zu sein.

Meine Frage lautet daher: Ist es möglich, Standardrichtlinien einzurichten und diese dann für bestimmte Controller und Aktionen zu überschreiben. Oder etwas ähnliches. Ich will damit nicht gehen

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

bei jedem Controller/jeder Aktion, da dies ein potenzielles Sicherheitsproblem darstellt. Was passiert, wenn ich versehentlich vergesse, die Rolle Admin zu setzen?.

25
regnauld

Sie müssen ein wenig mit dem Framework spielen, da Ihre globale Richtlinie restriktiver ist als die, die Sie auf bestimmte Controller und Aktionen anwenden möchten:

  • Standardmäßig können nur Admin Benutzer auf Ihre Anwendung zugreifen
  • Bestimmten Rollen wird auch Zugriff auf einige Controller gewährt (wie UserManagers Zugriff auf die UsersController)

Wie Sie bereits bemerkt haben, bedeutet ein globaler Filter, dass nur Admin Benutzer Zugriff auf einen Controller haben. Wenn Sie dem UsersController das zusätzliche Attribut hinzufügen, sind nur Benutzer beide Admin und UserManager haben Zugriff.

Es ist möglich, einen ähnlichen Ansatz wie bei MVC 5 zu verwenden, dies funktioniert jedoch auf andere Weise.

  • In MVC 6 enthält das Attribut [Authorize] keine Berechtigungslogik.
  • Stattdessen ist AuthorizeFilter die Methode OnAuthorizeAsync, die den Autorisierungsdienst aufruft, um sicherzustellen, dass die Richtlinien erfüllt sind.
  • Ein spezifisches IApplicationModelProvider wird verwendet, um ein AuthorizeFilter für jeden Controller und jede Aktion hinzuzufügen, die ein [Authorize] - Attribut haben.

Eine Möglichkeit wäre, Ihr IsAdminOrAuthorizeAttribute neu zu erstellen, diesmal jedoch als AuthorizeFilter, das Sie dann als globalen Filter hinzufügen:

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

Dies würde Ihren globalen Filter nur anwenden, wenn der Controller/die Aktion kein bestimmtes [Authorize] Attribut hat.


Sie können einen globalen Filter auch vermeiden, indem Sie sich in den Prozess einarbeiten, der die Filter generiert, die für jeden Controller und jede Aktion angewendet werden sollen. Sie können entweder Ihr eigenes IApplicationModelProvider oder Ihr eigenes IApplicationModelConvention hinzufügen. Mit beiden können Sie bestimmte Controller- und Aktionsfilter hinzufügen/entfernen.

Beispielsweise können Sie eine Standardautorisierungsrichtlinie und zusätzliche spezifische Richtlinien definieren:

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

Dann können Sie ein neues IApplicatioModelProvider erstellen, das jedem Controller, der kein eigenes [Authorize] - Attribut hat, die Standardrichtlinie hinzufügt Das Framework soll erweitert werden. Ich habe nur schnell das vorhandene AuthorizationApplicationModelProvider als Leitfaden verwendet.):

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

Mit dieser Option wird die Standardrichtlinie auf diesen beiden Controllern verwendet:

public class FooController : Controller

[Authorize]
public class BarController : Controller

Und die spezifische Benutzerrichtlinie wird hier verwendet:

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

Beachten Sie, dass Sie weiterhin jeder Richtlinie die Administratorrolle hinzufügen müssen, aber mindestens alle Ihre Richtlinien in einer einzigen Startmethode deklariert werden. Sie könnten wahrscheinlich Ihre eigenen Methoden zum Erstellen von Richtlinien erstellen, die immer die Administratorrolle hinzufügen.

35
Daniel J.G.

Mit @ Daniels Lösung bin ich auf dasselbe Problem gestoßen, das von @TarkaDaal im Kommentar erwähnt wurde (es gibt 2 AuthorizeFilter im Kontext für jeden Aufruf ... nicht ganz sicher, woher sie kommen).

Mein Weg, es zu lösen, ist also wie folgt:

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

Dies ist hässlich, funktioniert jedoch in diesem Fall, da Sie, wenn Sie nur das Authorize-Attribut ohne Argumente verwenden, ohnehin vom new AuthorizationPolicyBuilder().RequireRole("admin").Build() -Filter behandelt werden.

1
cheesemacfly