Freigeben über


Anbieter von benutzerdefinierten Autorisierungsrichtlinien mit IAuthorizationPolicyProvider in ASP.NET Core

Von Mike Rousos

Bei Verwendung der richtlinienbasierten Autorisierung werden Richtlinien in der Regel registriert, indem AuthorizationOptions.AddPolicy im Rahmen der Autorisierungsdienstkonfiguration aufgerufen wird. In einigen Szenarien ist es eventuell nicht möglich (oder wünschenswert), alle Autorisierungsrichtlinien auf diese Weise zu registrieren. In diesen Fällen können Sie über einen benutzerdefinierten IAuthorizationPolicyProvider steuern, wie Autorisierungsrichtlinien bereitgestellt werden.

Beispiele für Szenarien, in denen ein benutzerdefinierter IAuthorizationPolicyProvider nützlich sein kann, umfassen etwa:

  • Richtlinienauswertung über einen externen Dienst
  • Verwendung vieler Richtlinien (z. B. für unterschiedliche Zimmernummern oder Altersstufen), sodass es nicht sinnvoll ist, jede einzelne Autorisierungsrichtlinie mit einem Aufruf von AuthorizationOptions.AddPolicy hinzuzufügen
  • Erstellen von Richtlinien zur Laufzeit basierend auf Informationen in einer externen Datenquelle (wie einer Datenbank) oder dynamisches Festlegen von Autorisierungsanforderungen über einen anderen Mechanismus

Im AspNetCore-GitHub-Repository können Sie Beispielcode anzeigen oder herunterladen. Laden Sie die ZIP-Datei des dotnet/AspNetCore-Repositorys herunter. Entzippen Sie die Datei. Navigieren Sie zum Projektordner src/Security/samples/CustomPolicyProvider.

Anpassen des Richtlinienabrufs

ASP.NET Core-Apps verwenden eine Implementierung der IAuthorizationPolicyProvider-Schnittstelle, um Autorisierungsrichtlinien abzurufen. Standardmäßig wird DefaultAuthorizationPolicyProvider registriert und verwendet. DefaultAuthorizationPolicyProvider gibt Richtlinien von den AuthorizationOptions zurück, die in einem IServiceCollection.AddAuthorization-Aufruf bereitgestellt werden.

Passen Sie dieses Verhalten an, indem Sie eine andere IAuthorizationPolicyProvider-Implementierung im Dependency Injection-Container der App registrieren.

Die IAuthorizationPolicyProvider-Schnittstelle enthält drei APIs:

  • GetPolicyAsync gibt eine Autorisierungsrichtlinie für einen angegebenen Namen zurück.
  • GetDefaultPolicyAsync gibt die Standardautorisierungsrichtlinie zurück (die Richtlinie, die für [Authorize]-Attribute verwendet wird, wenn keine Richtlinie angegeben wurde).
  • GetFallbackPolicyAsync gibt die Fallback-Autorisierungsrichtlinie zurück (die Richtlinie, die von der Autorisierungsmiddleware verwendet wird, wenn keine Richtlinie angegeben wurde).

Durch die Implementierung dieser APIs können Sie die Bereitstellung von Autorisierungsrichtlinien anpassen.

Beispiel für parametrisierte Autorisierungsattribute

Ein Szenario, in dem IAuthorizationPolicyProvider nützlich ist, ist die Aktivierung benutzerdefinierter [Authorize]-Attribute, deren Anforderungen von einem Parameter abhängig sind. Beispielsweise wurde in der Dokumentation zur richtlinienbasierten Autorisierung eine altersbasierte Richtlinie („AtLeast21“) als Beispiel verwendet. Wenn in einer App Benutzer*innen unterschiedlichen Alters verschiedene Controlleraktionen zur Verfügung gestellt werden sollen, kann es hilfreich sein, viele verschiedene altersbasierte Richtlinien zu verwenden. Anstatt in AuthorizationOptions alle von der Anwendung benötigten unterschiedlichen altersbasierten Richtlinien zu registrieren, können Sie die Richtlinien dynamisch mit einem benutzerdefinierten IAuthorizationPolicyProvider generieren. Zur einfacheren Verwendung der Richtlinien können Sie Aktionen mit einem benutzerdefinierten Autorisierungsattribut wie [MinimumAgeAuthorize(20)] kommentieren.

Benutzerdefinierte Autorisierungsattribute

Autorisierungsrichtlinien werden durch ihre Namen identifiziert. Das zuvor beschriebene benutzerdefinierte MinimumAgeAuthorizeAttribute muss Argumente einer Zeichenfolge zuordnen, über die die entsprechende Autorisierungsrichtlinie abgerufen werden kann. Dies können Sie durch Ableiten von AuthorizeAttribute und Umschließen der AuthorizeAttribute.Policy-Eigenschaft durch die Age-Eigenschaft erreichen.

internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
    const string POLICY_PREFIX = "MinimumAge";

    public MinimumAgeAuthorizeAttribute(int age) => Age = age;

    // Get or set the Age property by manipulating the underlying Policy property
    public int Age
    {
        get
        {
            if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
            {
                return age;
            }
            return default(int);
        }
        set
        {
            Policy = $"{POLICY_PREFIX}{value.ToString()}";
        }
    }
}

Dieser Attributtyp verfügt über die Zeichenfolge Policy basierend auf dem hartcodierten Präfix ("MinimumAge") und einem Integer, der über den Konstruktor übergeben wird.

Sie können ihn auf Aktionen ebenso anwenden wie andere Authorize-Attribute, mit der Ausnahme, dass ein Integer als Parameter verwendet wird.

[MinimumAgeAuthorize(10)]
public IActionResult RequiresMinimumAge10()

Benutzerdefinierter IAuthorizationPolicyProvider

Mit dem benutzerdefinierten MinimumAgeAuthorizeAttribute ist das Anfordern von Autorisierungsrichtlinien für jedes gewünschte Mindestalter eine einfache Aufgabe. Als Nächstes muss sichergestellt werden, dass Autorisierungsrichtlinien für alle diese Altersgruppen verfügbar sind. Hierbei ist ein IAuthorizationPolicyProvider nützlich.

Bei Verwendung von MinimumAgeAuthorizationAttribute entsprechen die Namen der Autorisierungsrichtlinien dem Muster "MinimumAge" + Age. Daher sollte der benutzerdefinierte IAuthorizationPolicyProvider Autorisierungsrichtlinien wie folgt generieren:

  • Analysieren des Alters anhand des Richtliniennamens
  • Erstellen einer neuen AuthorizationPolicy mit AuthorizationPolicyBuilder
  • In diesem Beispiel und den folgenden Beispiel wird davon ausgegangen, dass Benutzer*innen über ein cookie authentifiziert werden. Der AuthorizationPolicyBuilder sollte mit mindestens einem Autorisierungsschemanamen erstellt werden oder immer erfolgreich sein. Andernfalls gibt es keine Informationen zum Bereitstellen einer Aufforderung für die Benutzer*innen, und eine Ausnahme wird ausgelöst.
  • Hinzufügen von Anforderungen zur Richtlinie basierend auf dem Alter mit AuthorizationPolicyBuilder.AddRequirements. In anderen Szenarien können Sie stattdessen RequireClaim, RequireRole oder RequireUserName verwenden.
internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
    const string POLICY_PREFIX = "MinimumAge";

    // Policies are looked up by string name, so expect 'parameters' (like age)
    // to be embedded in the policy names. This is abstracted away from developers
    // by the more strongly-typed attributes derived from AuthorizeAttribute
    // (like [MinimumAgeAuthorize()] in this sample)
    public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
            int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
        {
            var policy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme);
            policy.AddRequirements(new MinimumAgeRequirement(age));
            return Task.FromResult(policy.Build());
        }

        return Task.FromResult<AuthorizationPolicy>(null);
    }
}

Mehrere Autorisierungsrichtlinienanbieter

Beachten Sie bei der Verwendung benutzerdefinierter IAuthorizationPolicyProvider-Implementierungen, dass ASP.NET Core nur eine Instanz von IAuthorizationPolicyProvider verwendet. Wenn ein benutzerdefinierter Anbieter nicht in der Lage ist, Autorisierungsrichtlinien für alle verwendeten Richtliniennamen bereitzustellen, sollte er auf einen anderen Anbieter zurückgreifen.

Beispiel: Eine Anwendung benötigt sowohl benutzerdefinierte Altersrichtlinien als auch herkömmlichen rollenbasierten Richtlinienabruf. Eine solche Anwendung könnte einen benutzerdefinierten Autorisierungsrichtlinienanbieter verwenden, der folgendermaßen vorgeht:

  • Er versucht, die Richtliniennamen zu analysieren.
  • Er ruft einen anderen Richtlinienanbieter auf (z. B. DefaultAuthorizationPolicyProvider), wenn der Richtlinienname kein Alter enthält.

Die oben dargestellte Beispielimplementierung von IAuthorizationPolicyProvider kann für die Verwendung des DefaultAuthorizationPolicyProvider aktualisiert werden, indem ein zweiter Richtlinienanbieter im Konstruktor erstellt wird. Dieser dient für den Fall, dass der Richtlinienname nicht mit dem erwarteten Muster „MinimumAge“ + Alter übereinstimmt.

private DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }

public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
{
    // ASP.NET Core only uses one authorization policy provider, so if the custom implementation
    // doesn't handle all policies it should fall back to an alternate provider.
    BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}

Anschließend kann die GetPolicyAsync-Methode so aktualisiert werden, dass der BackupPolicyProvider verwendet wird, anstatt NULL zurückzugeben:

...
return BackupPolicyProvider.GetPolicyAsync(policyName);

Standardrichtlinie

Zusätzlich zur Bereitstellung benannter Autorisierungsrichtlinien muss ein benutzerdefinierter IAuthorizationPolicyProviderGetDefaultPolicyAsync implementieren, damit eine Autorisierungsrichtlinie für [Authorize]-Attribute ohne angegebenen Richtliniennamen bereitgestellt wird.

In vielen Fällen erfordert dieses Autorisierungsattribut lediglich eine/n authentifizierte/n Benutzer*in, sodass Sie die erforderliche Richtlinie durch einen Aufruf von RequireAuthenticatedUser erstellen können:

public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => 
    Task.FromResult(new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());

Wie bei allen Aspekten eines benutzerdefinierten IAuthorizationPolicyProvider können Sie dies nach Bedarf anpassen. In einigen Fällen kann es wünschenswert sein, die Standardrichtlinie von einem Fallback-IAuthorizationPolicyProvider abzurufen.

Fallbackrichtlinie

Ein benutzerdefinierter IAuthorizationPolicyProvider kann optional GetFallbackPolicyAsync implementieren, um eine Richtlinie bereitzustellen, die beim Kombinieren von Richtlinien und bei nicht angegebenen Richtlinien verwendet wird. Wenn GetFallbackPolicyAsync nicht NULL, sondern eine Richtlinie zurückgibt, wird die zurückgegebene Richtlinie von der Autorisierungsmiddleware verwendet, wenn keine Richtlinien für die Anforderung angegeben werden.

Wenn keine Fallbackrichtlinie erforderlich ist, kann der Anbieter null zurückgeben oder auf den Fallbackanbieter zurückgreifen:

public Task<AuthorizationPolicy> GetFallbackPolicyAsync() => 
    Task.FromResult<AuthorizationPolicy>(null);

Verwenden eines benutzerdefinierten IAuthorizationPolicyProvider

Um benutzerdefinierte Richtlinien von einem IAuthorizationPolicyProvider zu verwenden, müssen Sie Folgendes ausführen:

  • Registrieren Sie die entsprechenden AuthorizationHandler-Typen mit Dependency Injection (beschrieben unter Richtlinienbasierte Autorisierung) wie bei allen richtlinienbasierten Autorisierungsszenarien.

  • Registrieren Sie den benutzerdefinierten IAuthorizationPolicyProvider-Typ in der Dependency Injection-Dienstsammlung der App in Startup.ConfigureServices, um den Standardrichtlinienanbieter zu ersetzen.

    services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
    

Ein vollständiges Beispiel für einen benutzerdefinierten IAuthorizationPolicyProvider ist im dotnet/aspnetcore-GitHub-Repository verfügbar.