Ressourcenbasierte Autorisierung in ASP.NET Core MVC

In diesem Artikel wird beschrieben, wie Sie Benutzer für den Zugriff auf App-Ressourcen autorisieren.

In einer App wird eine Ressource in der Regel durch eine C#-Klasse dargestellt, die daten enthält, die in einer Sammlung gespeichert sind, z. B. ein byte[] Array. Die Klasse enthält in der Regel zusätzliche Metadaten, die sich auf die Ressource beziehen, z. B. einen eindeutigen Ressourcenbezeichner, Datumsangaben, Autoren, Quellinformationen und einen Anzeigenamen für die Anzeige in einer Benutzeroberfläche. Die Sammlung, die Ressourcendaten enthält, wird in der Regel aus physischen Dateiinhalten, einem Cloudspeicherobjekt, einem Speicherobjekt oder Daten aus einer Datenbank geladen.

Die ressourcenbasierte Autorisierung erfordert besondere Aufmerksamkeit in ASP.NET Core Apps. Die Attributauswertung erfolgt vor der Datenbindung und vor der Ausführung einer Aktion, die eine Ressource lädt. Die deklarative Autorisierung mit einem [Authorize] Attribut reicht nicht für die ressourcenbasierte Autorisierung aus. Stattdessen muss die App eine benutzerdefinierte Autorisierungsmethode aufrufen – ein Ansatz, der als imperative Autorisierung bezeichnet wird.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Unter Erstellen einer ASP.NET Core-App mit Benutzerdaten, die durch Autorisierung geschützt sind finden Sie eine Beispiel-App mit ressourcenbasierter Autorisierung.

Beispiele in diesem Artikel verwenden primäre Konstruktoren, die in C# 12 (.NET 8) oder höher verfügbar sind. Weitere Informationen finden Sie unter Declare primary constructors for classes and structs (C# documentation tutorial) und Primary constructors (C# Guide). Beispiel-Apps, die den Artikel begleiten und für Versionen von .NET vor .NET 8 vorgesehen sind, verwenden die Konstruktorinjektion.

Verwenden der imperativen Autorisierung

Die Autorisierung wird als IAuthorizationService implementiert, das beim Start der App durch das ASP.NET Core-Framework in der Dienstesammlung registriert wird. Der Dienst wird Klassen und Aktionen über Dependency Injection bereitgestellt. Der folgende Controller fügt außerdem ein Dokument-Repository ein, das der Entwickler im Dienstcontainer erstellt und registriert, um Dokumentvorgänge zu verwalten:

public class DocumentController(IAuthorizationService authorizationService,
    IDocumentRepository documentRepository) : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
        IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

    ...
}

IAuthorizationService verfügt über zwei AuthorizeAsync Methodenüberladungen. Eine der Überladungen akzeptiert einen Ressourcen- und Richtliniennamen:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource, 
    string policyName);

Die andere Überladung akzeptiert eine Ressource und eine Sammlung von Anforderungen (IAuthorizationRequirement), die ausgewertet werden sollen:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource,
    IEnumerable<IAuthorizationRequirement> requirements);

Im folgenden Beispiel wird die gesicherte Ressource in ein benutzerdefiniertes Document Objekt geladen. Eine AuthorizeAsync-Überladung wird aufgerufen, um zu bestimmen, ob der aktuelle Benutzer das Dokument anhand einer benutzerdefinierten "EditPolicy"-Autorisierungsrichtlinie bearbeiten darf. Wenn authorizationResult.Succeededtrue wahr ist, ist der Benutzer zum Zugriff auf das Dokument berechtigt, weil er das Dokument verfasst hat (Document.Author entspricht der Name des Benutzers).

Note

Im folgenden Beispiel wird davon ausgegangen, dass die Authentifizierung mit dem User Eigenschaftensatz erfolgreich ist.

[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = _documentRepository.Find(documentId);

    ...

    var authorizationResult = await _authorizationService
        .AuthorizeAsync(User, document, "EditPolicy");

    ...
}

Erstellen eines ressourcenbasierten Handlers

Das Erstellen eines ressourcenbasierten Autorisierungshandlers ähnelt dem Erstellen eines einfachen Anforderungshandlers. Erstellen Sie eine benutzerdefinierte Anforderungsklasse, und implementieren Sie eine Anforderungshandlerklasse. Weitere Informationen zum Erstellen einer Anforderungsklasse finden Sie in der richtlinienbasierten Autorisierung: Anforderungen.

Die Handlerklasse gibt die Anforderung und den Ressourcentyp an. Im folgenden Beispiel wird ein Handler veranschaulicht, der eine SameAuthorRequirement Anforderung und eine Document Ressource verwendet:

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Stellen Sie sich im vorherigen Beispiel vor, dass es sich bei SameAuthorRequirement um einen Sonderfall einer generischeren SpecificAuthorRequirement-Klasse handelt. Die (nicht abgebildete) SpecificAuthorRequirement-Klasse enthält eine Name-Eigenschaft, die den Namen des Erstellers darstellt. Die Name-Eigenschaft kann auf den aktuellen Benutzer festgelegt werden.

Registrieren Sie die Anforderung und den Handler in Program.cs:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Registrieren Sie die Anforderung und den Handler in Startup.ConfigureServices:

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Weitere Informationen zum Erstellen von Autorisierungsrichtlinien finden Sie unter Policy-basierte Autorisierung in ASP.NET Core.

Betriebliche Anforderungen

Um Entscheidungen basierend auf den Ergebnissen von CRUD-Vorgängen (Create, Read, Update, Delete) zu treffen, verwenden Sie die OperationAuthorizationRequirement Hilfsklasse. Mit dieser Klasse können Sie für jeden Vorgangstyp einen einzelnen Handler anstelle einer einzelnen Klasse schreiben. Geben Sie zur Verwendung einige Vorgangsnamen an:

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

Der Handler wird wie folgt mithilfe einer OperationAuthorizationRequirement-Anforderung und einer Document-Ressource implementiert:

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Der vorangehende Handler überprüft den Vorgang mithilfe der Ressource, der Identität des Benutzers und der Name Eigenschaft der Anforderung.

Anfechten und Verbieten mit einem operativen Ressourcen-Handler

In diesem Abschnitt wird gezeigt, wie die Ergebnisse der Aktionen „Anfechten“ und „Verbieten“ verarbeitet werden und worin sich „Anfechten“ und „Verbieten“ unterscheiden.

Wenn die Autorisierung fehlschlägt, der Benutzer jedoch authentifiziert ist, kann die App ein ForbidResult zurückgeben, das die Authentifizierungs-Middleware darüber informiert, dass die Autorisierung fehlgeschlagen ist. Gibt einen ChallengeResult für nicht authentifizierte Benutzer zurück. Für interaktive Browserclients kann es sinnvoll sein, den Benutzer zu einer Anmeldeseite umzuleiten.

Note

Im folgenden Beispiel wird davon ausgegangen, dass die Authentifizierung mit dem User Eigenschaftensatz erfolgreich ist.

if ((await _authorizationService
    .AuthorizeAsync(User, document, Operations.Read)).Succeeded)
{
    return View(document);
}
else if (User.Identity?.IsAuthenticated ?? false)
{
    return new ForbidResult();
}
else
{
    return new ChallengeResult();
}