Richtlinienbasierte Autorisierung in ASP.NET Core

Eine ASP.NET Core Autorisierungsrichtlinie ist eine Reihe von mindestens einer Autorisierungsanforderung, die das Framework auswertet, um zu entscheiden, ob ein Benutzer auf eine Ressource zugreifen darf. Eine Richtlinie kann mit einem Namen registriert und auf Ressourcen angewendet werden, die sie benötigen.

In diesem Artikel erfahren Sie:

  • Wie authentifizierte Benutzer standardmäßig erforderlich sind.
  • Erstellen von Anforderungen
  • So registrieren und wenden Sie Richtlinien an.
  • Wie benannte, Standard- und Fallbackrichtlinien ausgewählt werden.
  • Autorisierungshandler für die Auswertung einzelner und mehrerer Anforderungen.
  • Wie mehrere Anforderungen in einer einzelnen Richtlinie ausgewertet werden.

Eine benannte Richtlinie wird mit [Authorize(Policy = "...")] (Razor Komponenten, Seiten und Controllern) oder RequireAuthorization(...) (Endpunkten) angewendet, und das Framework verwendet Handler, um die Anforderungen hinter einer Richtlinie auszuwerten. IAuthorizationPolicyProvider(Benutzerdefinierte Autorisierungsrichtlinienanbieter in ASP.NET Core Dokumentation) generiert Richtlinien dynamisch, anstatt sie beim App-Start zu registrieren.

Rollenbasierte Autorisierung und anspruchsbasierte Autorisierung verwenden eine Anforderung, einen Anforderungshandler und eine vorkonfigurierte Autorisierungsrichtlinie. Diese Bausteine unterstützen die Erstellung von Autorisierungsauswertungen im Code.

In diesem Artikel werden Razor Komponentenbeispiele verwendet und sich auf Blazor Autorisierungsszenarien für ASP.NET Core 3.1 oder höher konzentriert. Informationen Razor zu Seiten und MVC-Anleitungen, die für alle Versionen von ASP.NET Core gelten, finden Sie in den folgenden Ressourcen nach dem Lesen dieses Artikels:

Einige Beispiele in diesem Artikel (ASP.NET Core 8.0 oder höher) 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).

Globale Benutzerauthentifizierung erforderlich

Legen Sie für eine serverseitige App, für die die meisten oder alle Endpunkte eine Authentifizierung erfordern, eine Fallbackrichtlinie fest, die einen authentifizierten Benutzer erfordert. Dieser sichere standardmäßige Ansatz schützt neu hinzugefügte Endpunkte, die keine Autorisierungsmetadaten angeben.

Die Fallbackrichtlinie, nicht die Standardrichtlinie, gilt für Endpunkte, die keine Autorisierungsmetadaten angeben. Die Standardrichtlinie gilt, wenn ein Endpunkt sie mit [Authorize] oder RequireAuthorization() ohne Richtliniennamen auswählt. Vollständige Richtlinienauswahlregeln finden Sie im Abschnitt "Standard- und Fallbackrichtlinien ".

var requireAuthPolicy = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();

builder.Services.AddAuthorizationBuilder()
    .SetFallbackPolicy(requireAuthPolicy);
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

In Startup.ConfigureServices:

services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Wenden Sie AllowAnonymous() an oder rufen Sie [AllowAnonymous] für Endpunkte auf, die absichtlich öffentlich sind.

Die Fallbackrichtlinie gilt für Anforderungen, die von der Autorisierungs-Middleware verarbeitet werden. Beispiel:

  • Eine Anforderung, die keinem Endpunkt entspricht, verwendet die Fallbackrichtlinie, wenn die Autorisierungs-Middleware für die Anforderung ausgeführt wird.
  • Statische Dateien, die von der Middleware für statische Dateien bereitgestellt werden, bevor die Autorisierungsmiddleware ausgeführt wird, sind nicht durch die Fallbackrichtlinie geschützt.
  • Öffentliche Endpunkte können von statischen Ressourcen abhängen, die auch anonymen Zugriff zulassen müssen.

Weitere Informationen finden Sie unter Statische Dateien in ASP.NET Core und serverseitigen Blazor App-Autorisierungsmustern. Blazor WebAssembly Apps unterstützen keine serverseitige Fallbackautorisierungsrichtlinie. Informationen zu Blazor WebAssembly Autorisierungsmustern finden Sie unter Secure ASP.NET Core Blazor WebAssembly.

Standard- und Ausweichrichtlinien

Die Autorisierungs-Middleware kombiniert die Autorisierungsmetadaten für einen Endpunkt in einer Richtlinie. In der folgenden Tabelle wird beschrieben, wie die Metadaten bestimmen, welche Richtlinie verwendet wird:

Autorisierungsmetadaten Richtlinie oder Verhalten
None Das AuthorizationOptions.FallbackPolicy wird verwendet, wenn es konfiguriert ist. Standardmäßig ist die Fallbackrichtlinie null, sodass keine Autorisierung erforderlich ist.
[Authorize] oder RequireAuthorization() ohne Richtlinienname Die AuthorizationOptions.DefaultPolicy wird verwendet, es sei denn, der Endpunkt hat ebenfalls eine explizite AuthorizationPolicy Instanz. Standardmäßig erfordert die Standardrichtlinie einen authentifizierten Benutzer.
[Authorize(Policy = "{POLICY NAME}")] oder RequireAuthorization("{POLICY NAME}") Die angegebene Richtlinie wird verwendet.
[Authorize(Roles = "{ROLES}")] Eine Richtlinie wird mit den angegebenen Rollen erstellt. Die Standardrichtlinie wird für diese Autorisierungsdeklaration nicht hinzugefügt.
RequireAuthorization(policy) mit einer AuthorizationPolicy Instanz Die explizite Richtlinie wird verwendet. Wenn explizite Richtlinienmetadaten vorhanden sind, fügen bloße Autorisierungsdaten und Autorisierungsdaten, die nur ein Authentifizierungsschema angeben, nicht die Standardrichtlinie hinzu.
[Authorize(AuthenticationSchemes = "{SCHEME}")] ohne Namen der Richtlinie oder Rollen Das angegebene Authentifizierungsschema wird verwendet. Die Standardrichtlinie wird ebenfalls verwendet, es sei denn, der Endpunkt verfügt über eine explizite AuthorizationPolicy-Instanz.
Mehrere [Authorize] Attribute oder richtlinienauswählende RequireAuthorization(...) Aufrufe Die ausgewählten benannten Richtlinien, Rollen, Authentifizierungsschemas und explizite Richtlinien werden kombiniert. Bare Autorisierungsdaten fügen die Standardrichtlinie nur hinzu, wenn keine explizite AuthorizationPolicy Instanz vorhanden ist. Alle Anforderungen in der kombinierten Richtlinie müssen erfolgreich sein. Die Fallbackrichtlinie wird nicht verwendet.
[AllowAnonymous] oder AllowAnonymous() Die Autorisierungs-Middleware setzt für den Endpunkt keinen Autorisierungsfehler durch. Die Authentifizierung kann weiterhin ausgeführt werden und HttpContext.User mit Daten füllen.

Die Fallbackrichtlinie wird nicht mit einer benannten oder Standardrichtlinie kombiniert. Für die in der vorherigen Tabelle gezeigten Deklarationen wird sie nur ausgewählt, wenn keine Autorisierungsrichtlinie aus den Autorisierungsmetadaten des Endpunkts erstellt wird. Verwendet z. B [Authorize] . die Standardrichtlinie anstelle der Fallbackrichtlinie und [Authorize(Policy = "{POLICY NAME}")] verwendet die benannte Richtlinie anstelle der Fallbackrichtlinie.

Anforderungen, die über IAuthorizationRequirementData als Endpunktmetadaten bereitgestellt werden, werden nach der Richtlinienauswahl hinzugefügt. Wenn keine anderen Autorisierungsmetadaten eine Richtlinie erzeugen, werden diese Anforderungen mit der Fallbackrichtlinie kombiniert, wenn eine Fallbackrichtlinie konfiguriert ist.

In ASP.NET Core 8.0 bis 10.0 werden Attribute, die IAuthorizationRequirementData implementieren, nur bei Minimal APIs und routenbasierten Endpunkten durchgesetzt. Informationen zur Unterstützung von Versionen und Hostingmodellen finden Sie unter Benutzerdefinierte Autorisierungsrichtlinien mit `IAuthorizationRequirementData`.

Anforderungen und Registrierung von Richtlinien

Eine Autorisierungsrichtlinie besteht aus einer oder mehreren Anforderungen, die von einer Richtlinie verwendet werden, um die Autorisierung für den aktuellen Benutzerprinzipal auszuwerten. Eine Anforderung implementiert IAuthorizationRequirement, bei der es sich um eine leere Markerschnittstelle handelt.

Wenn eine Anforderung weder Daten enthält noch Eigenschaften (Parameter) besitzt, fungiert sie als leere Markierung, um einen zugeordneten Autorisierungshandler (IAuthorizationHandler) zur Verarbeitung der Autorisierung auszulösen (wie weiter unten in diesem Artikel ausführlich beschrieben). Da der Handler in diesem Fall vollständig vom HTTP-Kontext, benutzeransprüchen oder Back-End-Daten abhängt, um eine Entscheidung über die Anforderung des Benutzers zu treffen, erfordert die Anforderungsklasse selbst keine internen Daten oder Parameter. Die Anforderung weist nur das Framework an, welche Regel ausgewertet werden soll.

Betrachten Sie beispielsweise die folgende Mindestaltersanforderung (MinimumAgeRequirement), die lediglich als Markerklasse implementiert wird:

public class MinimumAgeRequirement : IAuthorizationRequirement { }

Die vorherige Anforderung wird verwendet, um eine Richtlinie zu erstellen, die bestätigt, dass der Benutzer über ein bestimmtes Alter verfügt, das der Handler überprüft. Ein AuthorizationHandler<MinimumAgeRequirement> prüft AuthorizationHandlerContext.User. Wenn der Benutzer einen Anspruch auf das Geburtsdatum hat, aus dem hervorgeht, dass er älter als ein bestimmtes Alter ist, gilt die Anforderung als erfüllt. Für das Anforderungsobjekt sind in diesem Fall keine Eigenschaften (Parameter) erforderlich. Im nächsten Beispiel wird die vollständige Implementierung einer Mindestaltersanforderung veranschaulicht, die über einen Parameter zum Festlegen des Mindestalters verfügt.

Berücksichtigen Sie die folgende MinimumAgeRequirement Anforderung, die einen einzelnen Parameter, ein Mindestalter, beschreibt, um die Benutzerautorisierung auszuwerten:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
    public int MinimumAge { get; } = minimumAge;
}
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

Eine Richtlinie wird als Teil der Autorisierungsdienstkonfiguration in der App-Datei Program durch Aufrufen AuthorizationBuilder.AddPolicyregistriert. Im folgenden Beispiel wird eine AtLeast21 Richtlinie mit einer einzigen Anforderung eines Mindestalters erstellt und das Mindestalter auf 21 Jahre festgelegt.

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AtLeast21", policy => 
        policy.Requirements.Add(new MinimumAgeRequirement(21)));

Eine Richtlinie wird als Teil der Autorisierungsdienstkonfiguration in der App-Datei Program durch Aufrufen AuthorizationBuilder.AddPolicyregistriert. Im folgenden Beispiel wird eine AtLeast21 Richtlinie mit einer einzigen Anforderung eines Mindestalters erstellt und das Mindestalter auf 21 Jahre festgelegt:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Eine Richtlinie wird als Teil der Autorisierungsdienstkonfiguration in Startup.ConfigureServices (Startup.cs) durch Aufrufen von AuthorizationBuilder.AddPolicy registriert. Im folgenden Beispiel wird eine AtLeast21 Richtlinie mit einer einzigen Anforderung eines Mindestalters erstellt und das Mindestalter auf 21 Jahre festgelegt:

services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Wenn eine Autorisierungsrichtlinie mehrere Autorisierungsanforderungen enthält, müssen alle Anforderungen erfüllt sein, damit die Richtlinie erfolgreich ausgewertet wird. Mit anderen Worten, mehrere Autorisierungsanforderungen, die einer einzigen Autorisierungsrichtlinie hinzugefügt wurden, werden auf UND-Basis behandelt.

Anwenden von Richtlinien auf Razor Komponenten

Anwenden von Richtlinien auf Razor Komponenten mithilfe des [Authorize] Attributs mit dem Richtliniennamen:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]

Wenn mehrere Richtlinien angewendet werden, müssen alle Richtlinien erfüllt sein, bevor der Zugriff gewährt wird:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]

Anwenden von Richtlinien auf Endpunkte

Sie wenden Richtlinien auf Endpunkte an, indem Sie RequireAuthorization mit dem Richtliniennamen verwenden. Beispiel:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

Richtlinien in MVC-Apps und Razor Pages-Apps anwenden

Anleitungen zum Anwenden von Richtlinien in Razor Seiten und MVC-Apps finden Sie in den folgenden Ressourcen:

Autorisierungsdienstschnittstelle (IAuthorizationService)

IAuthorizationService ist in erster Linie dafür verantwortlich, zu bestimmen, ob die Autorisierung erfolgreich ist, wenn eine IAuthorizationService.AuthorizeAsync Überladung aufgerufen wird:

  • AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements): Überprüft, ob ein Benutzer einen bestimmten Satz von Autorisierungsanforderungen für eine angegebene Ressource erfüllt.
  • AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName): Überprüft, ob ein Benutzer eine bestimmte Autorisierungsrichtlinie für eine angegebene Ressource erfüllt.

Wenn eine Ressource für die Richtlinienauswertung nicht erforderlich ist, wird null als Ressource übergeben.

Die vorangehenden Methoden geben ein AuthorizationResult zurück, das in ein Task eingebettet ist.

Jeder IAuthorizationHandler ist dafür verantwortlich, zu prüfen, ob die Anforderungen über IAuthorizationHandler.HandleAsync erfüllt sind. Die AuthorizationHandlerContext Klasse enthält die autorisierungsinformationen, die von der IAuthorizationHandler Implementierung verwendet werden. IAuthorizationRequirement ist eine Markerschnittstelle ohne Methoden, die als Mechanismus zum Nachverfolgen der Erfolgreichkeit der Autorisierung dienen. Wenn AuthorizationHandlerContext.Succeed mit IAuthorizationRequirement aufgerufen wird, ist die Richtlinie erfüllt:

context.Succeed(requirement);

Autorisierungshandler

Autorisierungshandler sind für die Auswertung der Eigenschaften einer Anforderung verantwortlich. Der Autorisierungshandler wertet die Anforderungen anhand einer bereitgestellten AuthorizationHandlerContext aus, um festzustellen, ob Zugriff zulässig ist.

Eine Anforderung kann über mehrere Handler verfügen. Ein Handler kann AuthorizationHandler<TRequirement> erben, wobei TRequirement die zu behandelnde Anforderung ist. Alternativ kann ein Handler IAuthorizationHandler auch direkt implementieren, um mehrere Anforderungstypen zu verarbeiten.

Verwenden Sie einen Handler für eine Anforderung

Das folgende Beispiel zeigt eine 1:1-Beziehung, in der ein Mindestalter-Handler eine einzelne Anforderung behandelt:

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = 
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = 
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
            // Use the following if targeting a version of
            // .NET Framework older than 4.6:
            // return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);

        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Der vorstehende Code ermittelt, ob der aktuelle Benutzerprinzipal einen Geburtsdatumsanspruch hat. Es kann keine Autorisierung erfolgen, wenn der Anspruch fehlt. In diesem Fall wird eine abgeschlossene Aufgabe zurückgegeben. Wenn ein Anspruch vorhanden ist, wird das Alter des Benutzers berechnet. Wenn der oder die Benutzer*in das durch die Anforderung definierte Mindestalter hat, gilt die Autorisierung als erfolgreich. Wenn die Autorisierung erfolgreich ist, wird context.Succeed mit der erfüllten Anforderung als einzigem Parameter aufgerufen.

Verwenden eines Handlers für mehrere Anforderungen

Das folgende Beispiel zeigt eine Eins-zu-viele-Beziehung, in der ein Berechtigungs-Handler drei verschiedene Arten von Anforderungen behandeln kann.

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                        requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

Der obige Code durchläuft PendingRequirements, eine Eigenschaft, die Anforderungen enthält, die als nicht erfolgreich markiert sind. Für eine ReadPermission-Anforderung muss der Benutzer entweder ein Eigentümer oder ein Sponsor sein, um auf die angeforderte Ressource zuzugreifen. Für eine EditPermission- oder DeletePermission-Anforderung müssen sie Eigentümer sein, um auf die angeforderte Ressource zuzugreifen.

Handlerregistrierung

Registrieren Sie Handler in der Dienstesammlung während der Konfiguration. Im folgenden Beispiel wird ein Handler für das Mindestalter (MinimumAgeHandler) als Singleton-Dienst registriert; ein Handler kann jedoch mit jeder der integrierten Dienstlebensdauern registriert werden:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

Es ist möglich, eine Anforderung und einen Handler in einer einzelnen Klasse zu bündeln, die sowohl IAuthorizationRequirement als auch IAuthorizationHandler implementiert. Diese Bündelung sorgt für eine enge Kopplung zwischen Handler und Anforderung und wird nur für einfache Anforderungen und Handler empfohlen. Wenn Sie eine Klasse erstellen, die beide Schnittstellen implementiert, entfällt die Notwendigkeit, den Handler im Dienstcontainer zu registrieren, dank der integrierten PassThroughAuthorizationHandler, die es Anforderungen ermöglicht, sich selbst zu behandeln.

Sehen Sie sich die Implementierung der ASP.NET Core-Klasse AssertionRequirement für ein Beispiel an, bei dem es AssertionRequirement sich sowohl um eine Anforderung als auch um den Handler in einer vollständig eigenständigen Klasse handelt. Die AssertionRequirement API des Frameworks ermöglicht es Ihnen, den Zugriff mithilfe von Inline-Lambda-Ausdrücken zu überprüfen, anstatt separate, textbausteine Anforderungs- und Handlerklassen zu schreiben.

Note

Dokumentationslinks zur .NET-Referenzquelle laden in der Regel die Standard-Branch des Repositorys, die die aktuelle Entwicklung der nächsten Version von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus.

Was sollten Handler zurückgeben?

Die Handle Methode im Handlerbeispiel gibt keinen Wert zurück. Wie wird ein erfolgreicher oder fehlerhafter Status angezeigt?

  • Ein Handler signalisiert Erfolg, indem er context.Succeed aufruft und die erfolgreich validierte Anforderung (IAuthorizationRequirement) übergibt.

  • Ein Handler ist nicht erforderlich, um Fehler im Allgemeinen zu behandeln, da andere Handler für dieselbe Anforderung möglicherweise erfolgreich sind.

  • Rufen Sie context.Fail auf, um das Scheitern zu garantieren, selbst wenn andere Anforderungshandler erfolgreich sind.

Wenn ein Handler context.Succeed oder context.Fail aufruft, werden weiterhin alle anderen Handler aufgerufen. Dies ermöglicht, dass Anforderungen Nebenwirkungen wie z. B. Protokollierung verursachen, die auch dann stattfinden, wenn ein anderer Handler eine Anforderung erfolgreich validiert oder bei ihr fehlschlägt. Bei Festlegung auf false umgeht die InvokeHandlersAfterFailure-Eigenschaft die Ausführung von Handlern, wenn context.Fail aufgerufen wird. InvokeHandlersAfterFailure ist standardmäßig auf true festgelegt, sodass alle Handler aufgerufen werden.

Note

Autorisierungshandler werden auch dann aufgerufen, wenn die Authentifizierung fehlschlägt. Außerdem können Handler in beliebiger Reihenfolge ausgeführt werden. Dies hängt daher nicht von der Reihenfolge der aufrufenden Handler ab.

Wann sollte ich mehrere Handler für eine Anforderung verwenden?

In Fällen, in denen die Auswertung auf OR-Basis erfolgen soll, implementieren Sie mehrere Handler für eine einzelne Anforderung. Nehmen Wir beispielsweise an, dass die Contoso Corporation Türen hat, die nur mit Schlüsselkarten geöffnet sind. Wenn Sie Ihre Schlüsselkarte zu Hause verlassen, druckt der Empfangsist einen temporären Aufkleber und öffnet die Tür für Sie. In diesem Szenario hat die App eine einzige Anforderung, aber mehrere Handler, die jeweils eine einzelne Anforderung untersuchen.

In den folgenden Beispielimplementierungen:

  • BuildingEntryRequirement ist die Voraussetzung für den Gebäudezutritt.
  • BadgeEntryHandler (die Person verfügt über einen Ausweis) und TemporaryStickerHandler (die Person hat einen temporären Aufkleber) sind separate Handler, die jeweils genau eine Bedingung prüfen.

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs:

using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs:

using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "TemporaryBadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                    BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "BadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
        BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "TemporaryBadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Stellen Sie sicher, dass beide Handler registriert sind. Wenn einer der Handler erfolgreich ist, wenn eine Richtlinie die BuildingEntryRequirement auswertet, ist die Richtlinienauswertung erfolgreich.

Verwenden einer Func zum Erfüllen einer Richtlinie

Es gibt Situationen, in denen die Erfüllung einer Richtlinie einfach im Code mit einem Func<AuthorizationHandlerContext, bool> Stellvertreter ausgedrückt werden kann, wenn eine Richtlinie mit dem RequireAssertion Richtlinien-Generator konfiguriert wird. Beispielsweise kann der vorherige BadgeEntryHandler Code wie folgt umgeschrieben werden:

    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://contososecurity")));
});

// <snippet_minimumAgeHandlerRegistration>
services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

Autorisierung über ein externes Dienstbeispiel

Die Autorisierung über ein externes Dienstbeispiel (dotnet/AspNetCore.Docs.Samples GitHub Repository) zeigt, wie zusätzliche Autorisierungsanforderungen mit einem externen Autorisierungsdienst implementiert werden. Das Projekt der Contoso.API Lösung wird durch Microsoft Entra ID gesichert. Eine zusätzliche Autorisierungsprüfung des Contoso.Security.API-Projekts gibt einen Rückgabewert zurück, der beschreibt, ob die Contoso.API-Client-App die GetWeather-API aufrufen kann.

Das Beispiel konfigurieren

Die folgende Demonstration basiert auf der Verwendung von NSwag (Swagger/OpenAPI) oder cURL in einer Befehlsshell.

Legen Sie im Contoso.Security.API Projekt den AllowedClients Platzhalter ({CLIENT ID}) auf einen beliebigen Test-GUID-Wert fest (z. B 00001111-aaaa-2222-bbbb-3333cccc4444. ):

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}"
  ]
}

Verwenden Sie in einer Befehlsshell, die für das Contoso.API-Projekt geöffnet ist, dotnet user-jwts, um ein Zugriffstoken mit einem appid-Claim für die ID der Client-App zu generieren, die im vorherigen Schritt erstellt wurde (z. B. 00001111-aaaa-2222-bbbb-3333cccc4444).

dotnet user-jwts create --claim appid={GUID}

Beispiel:

dotnet user-jwts create --claim appid=00001111-aaaa-2222-bbbb-3333cccc4444

Die Ausgabe erzeugt ein Token nach „Token:“ in der Befehlsshell:

New JWT saved with ID '{JWT ID}'.
Name: {USER}
Custom Claims: [appid=00001111-aaaa-2222-bbbb-3333cccc4444]

Token: {TOKEN}

Merken Sie sich den Wert des Tokens (wobei der {TOKEN} Platzhalter in der vorherigen Ausgabe angezeigt wird), damit Sie es später verwenden können.

Sie können das Token in einem Online-Decoder JWT, z. B. jwt.ms, decodieren, um dessen Inhalt anzuzeigen; daraus geht hervor, dass es einen appid-Anspruch mit der ID der Client-App enthält:

{
  "alg": "HS256",
  "typ": "JWT"
}.{
  "unique_name": "{USER}",
  "sub": "{USER}",
  "jti": "14ed7729",
  "appid": "{CLIENT ID}",
  "aud": [
    "https://localhost:7250",
    "http://localhost:7251"
  ],
  "nbf": 1780660887,
  "exp": 1788609687,
  "iat": 1780660888,
  "iss": "dotnet-user-jwts"
}.[Signature]

Führen Sie den Befehl erneut mit einem falschen Client-ID -Wert (appid) aus:

dotnet user-jwts create --claim appid=aaaabbbb-0000-cccc-1111-dddd2222eeee

Legen Sie den Wert des zweiten Tokens beiseite.

Starten Sie sowohl die Projekte Contoso.API und Contoso.Security.API in Visual Studio als auch mit dem Befehl dotnet watch in einer Befehlsshell:

dotnet watch

Wählen Sie auf der Benutzeroberfläche von Swagger des Contoso.API Projekts (https://localhost:7250/swagger/index.html) die Schaltfläche "Autorisieren" aus.

Geben Sie im Fenster "Verfügbare Autorisierungen: Bearer Fenster" das Zugriffstoken ein. Wählen Sie die Schaltfläche Autorisieren aus. Schließen Sie das Fenster "Verfügbare Autorisierungen ".

Wählen Sie unter default die Schaltfläche Get für den Endpunkt /WeatherForecast aus. Wählen Sie die Schaltfläche "Ausprobieren" aus . Wählen Sie die Schaltfläche "Ausführen" aus .

Die Ausgabe unter Antworten>Serverantwort>Antworttext zeigt die vom Contoso.API-Projekt zurückgegebene JSON-Wettervorhersage an.

Führen Sie dieselben Schritte mit dem Zugriffstoken aus, das mit einer ungültigen Client-App-ID generiert wurde. Die Antwort ist 403 - Verboten.