Richtlinienbasierte Autorisierung in ASP.NET Core

Im Weiteren verwenden die rollenbasierte Autorisierung und die anspruchsbasierte Autorisierung eine Anforderung, einen Anforderungshandler und eine vorkonfigurierte Richtlinie. Diese Bausteine unterstützen den Ausdruck von Autorisierungsauswertungen im Code. Das Ergebnis ist eine umfangreichere, wiederverwendbare, testbare Autorisierungsstruktur.

Eine Autorisierungsrichtlinie besteht aus mindestens einer Anforderung. Registrieren Sie ihn als Teil der Konfiguration des Autorisierungsdiensts in der Datei Program.cs der App:

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

Im vorherigen Beispiel wird eine Richtlinie "AtLeast21" erstellt. Es besteht eine einzelne Anforderung — eines Mindestalters, das als Parameter für die Anforderung bereitgestellt wird.

IAuthorizationService

Der primäre Dienst, der bestimmt, ob die Autorisierung erfolgreich war, ist IAuthorizationService :

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Im vorangehenden Code werden die beiden Methoden von IAuthorizationServicehervorgehoben.

IAuthorizationRequirement ist ein Markerdienst ohne Methoden und der Mechanismus zum Nachverfolgen, ob die Autorisierung erfolgreich ist.

Jeder IAuthorizationHandler ist für die Überprüfung verantwortlich, ob die Anforderungen erfüllt sind:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Die AuthorizationHandlerContext -Klasse wird vom Handler verwendet, um zu kennzeichnen, ob die Anforderungen erfüllt wurden:

 context.Succeed(requirement)

Der folgende Code zeigt die vereinfachte (und mit Kommentaren versehene) Standardimplementierungen des Autorisierungsdiensts:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

Der folgende Code zeigt eine typische Konfiguration des Autorisierungsdiensts:

// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...

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

// Configure your policies
builder.Services.AddAuthorization(options =>
      options.AddPolicy("Something",
      policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));

Verwenden Sie , oder für die IAuthorizationService [Authorize(Policy = "Something")] RequireAuthorization("Something") Autorisierung.

Anwenden von Richtlinien auf MVC-Controller

Informationen zu Apps, die Razor Pages verwenden, finden Sie im Abschnitt Anwenden von Richtlinien auf Razor Seiten.

Wenden Sie Richtlinien auf Controller an, indem Sie das [Authorize] -Attribut mit dem Richtliniennamen verwenden. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AuthorizationPoliciesSample.Controllers;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

Anwenden von Richtlinien auf Razor Seiten

Wenden Sie Richtlinien auf Razor Pages an, indem Sie das [Authorize] -Attribut mit dem Richtliniennamen verwenden. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationPoliciesSample.Pages;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }

Richtlinien können nicht auf Razor Seitenhandlerebene angewendet werden, sie müssen auf die Seite angewendet werden.

Richtlinien können auch Razor mithilfe einer Autorisierungskonventionauf Pages angewendet werden.

Anwenden von Richtlinien auf Endpunkte

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

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

Anforderungen

Eine Autorisierungsanforderung ist eine Sammlung von Datenparametern, die eine Richtlinie zum Auswerten des aktuellen Benutzerprinzipals verwenden kann. In unserer Richtlinie "AtLeast21" ist die Anforderung ein einzelner Parameter — für das Mindestalter. Eine Anforderung implementiert IAuthorizationRequirement,eine leere Markerschnittstelle. Eine parametrisierte Mindestaltersanforderung kann wie folgt implementiert werden:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

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

    public int MinimumAge { get; }
}

Wenn eine Autorisierungsrichtlinie mehrere Autorisierungsanforderungen enthält, müssen alle Anforderungen erfüllt sein, damit die Richtlinienauswertung erfolgreich ist. Anders ausgedrückt: Mehrere Autorisierungsanforderungen, die einer einzelnen Autorisierungsrichtlinie hinzugefügt werden, werden auf AND-Basis behandelt.

Hinweis

Eine Anforderung erfordert keine Daten oder Eigenschaften.

Autorisierungshandler

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

Eine Anforderung kann mehrere Handler aufweisen. Ein Handler kann AuthorizationHandler <TRequirement> erben,wobei TRequirement die Zu behandelnde Anforderung ist. Alternativ kann ein Handler IAuthorizationHandler direkt implementieren, um mehr als einen Anforderungstyp zu verarbeiten.

Verwenden eines Handlers für eine Anforderung

Das folgende Beispiel zeigt eine 1:1-Beziehung, in der ein Mindestaltershandler eine einzelne Anforderung verarbeitet:

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

namespace AuthorizationPoliciesSample.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 && c.Issuer == "http://contoso.com");

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

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

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

        return Task.CompletedTask;
    }
}

Der vorangehende Code bestimmt, ob der aktuelle Benutzerprinzipal über ein Geburtsdatum verfügt, das von einem bekannten und vertrauenswürdigen Aussteller ausgestellt wurde. Die Autorisierung kann nicht 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 Benutzer das durch die Anforderung definierte Mindestalter erfüllt, wird die Autorisierung als erfolgreich eingestuft. Wenn die Autorisierung erfolgreich ist, context.Succeed wird mit der erfüllten Anforderung als einzigem Parameter aufgerufen.

Verwenden eines Handlers für mehrere Anforderungen

Das folgende Beispiel zeigt eine 1:n-Beziehung, in der ein Berechtigungshandler drei verschiedene Arten von Anforderungen verarbeiten kann:

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

namespace AuthorizationPoliciesSample.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;
    }
}

Der vorangehende Code durchläuft PendingRequirements — eine Eigenschaft, die Anforderungen enthält, die nicht als erfolgreich markiert sind. Für eine ReadPermission Anforderung muss der Benutzer entweder Besitzer oder Sponsor sein, um auf die angeforderte Ressource zugreifen zu können. Im Fall einer EditPermission - oder DeletePermission -Anforderung muss er besitzer sein, um auf die angeforderte Ressource zugreifen zu können.

Handlerregistrierung

Registrieren Sie Handler während der Konfiguration in der Dienstauflistung. Beispiel:

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

Der vorangehende Code wird MinimumAgeHandler als Singleton registriert. Handler können mit einer der integrierten Dienstlebensdauernregistriert werden.

Es ist möglich, sowohl eine Anforderung als auch einen Handler in einer einzelnen Klasse zu bündeln, die sowohl als auch IAuthorizationRequirement IAuthorizationHandler implementiert. Dies sorgt für eine enge Kopplung zwischen dem Handler und der Anforderung und wird nur für einfache Anforderungen und Handler empfohlen. Durch das Erstellen einer Klasse, die beide Schnittstellen implementiert, muss der Handler aufgrund des integrierten PassThroughAuthorizationHandler,der anforderungen selbst verarbeiten kann, nicht mehr in der Di registriert werden.

Ein gutes Beispiel finden Sie in der AssertionRequirement-Klasse, bei der AssertionRequirement sowohl eine Anforderung als auch der Handler in einer vollständig eigenständigen Klasse ist.

Was sollte ein Handler zurückgeben?

Beachten Sie, dass die Handle -Methode im Handlerbeispiel keinen Wert zurückgibt. Wie wird ein Status von Erfolg oder Fehler angezeigt?

  • Ein Handler gibt den Erfolg durch Aufrufen von an context.Succeed(IAuthorizationRequirement requirement) und übergibt die Anforderung, die erfolgreich überprüft wurde.

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

  • Rufen Sie auf, um einen Fehler zu gewährleisten, auch wenn andere Anforderungshandler erfolgreich context.Fail sind.

Wenn ein Handler context.Succeed oder aufruft, werden alle anderen Handler weiterhin context.Fail aufgerufen. Dadurch können Anforderungen Nebeneffekte erzeugen, z. B. die Protokollierung, die auch dann ausgeführt wird, wenn ein anderer Handler eine Anforderung erfolgreich überprüft oder fehlgeschlagen hat. Wenn diese Eigenschaft auf festgelegt ist, wird false die Ausführung von Handlern durch die InvokeHandlersAfterFailure-Eigenschaft kurzgeschaltet, wenn context.Fail aufgerufen wird. InvokeHandlersAfterFailure ist standardmäßig auf true , in diesem Fall werden alle Handler aufgerufen.

Hinweis

Autorisierungshandler werden aufgerufen, auch wenn die Authentifizierung fehlschlägt.

Warum sollte ich mehrere Handler für eine Anforderung benötigen?

In Fällen, in denen die Auswertung auf OR-Basis erfolgen soll, implementieren Sie mehrere Handler für eine einzelne Anforderung. Microsoft verfügt beispielsweise über Türen, die nur mit Schlüsselkarten geöffnet werden. Wenn Sie Ihre Schlüsselkarte zu Hause lassen, druckt der Empfangsleiter einen temporären Aufdruck und öffnet die Tür für Sie. In diesem Szenario haben Sie eine einzelne Anforderung, BuildingEntry, aber mehrere Handler, von denen jeder eine einzelne Anforderung untersucht.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs

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

namespace AuthorizationPoliciesSample.Policies.Handlers;

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

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

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

namespace AuthorizationPoliciesSample.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://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

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

Verwenden einer Funkt zum Erfüllen einer Richtlinie

Es kann Situationen geben, in denen die Erfüllung einer Richtlinie einfach im Code ausgedrückt werden kann. Beim Func<AuthorizationHandlerContext, bool> Konfigurieren einer Richtlinie mit dem Richtlinien-Generator können Sie eine RequireAssertion bereitstellen.

Beispielsweise könnte der vorherige BadgeEntryHandler wie folgt umgeschrieben werden:

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

Zugreifen auf den MVC-Anforderungskontext in Handlern

Die HandleRequirementAsync -Methode verfügt über zwei Parameter: ein AuthorizationHandlerContext und das , das behandelt TRequirement wird. Frameworks wie MVC oder können der -Eigenschaft des SignalR -Objekts beliebige Objekte Resource AuthorizationHandlerContext hinzufügen, um zusätzliche Informationen zu übergeben.

Bei Verwendung des Endpunktroutings wird die Autorisierung in der Regel von der Autorisierungs-Middleware verarbeitet. In diesem Fall ist die Resource -Eigenschaft eine Instanz von HttpContext . Der Kontext kann für den Zugriff auf den aktuellen Endpunkt verwendet werden, der zum Untersuchen der zugrunde liegenden Ressource verwendet werden kann, an die Sie das Routing durchführen. Beispiel:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

Beim herkömmlichen Routing oder wenn die Autorisierung als Teil des Autorisierungsfilters von MVC erfolgt, ist der Wert von Resource eine AuthorizationFilterContext -Instanz. Diese Eigenschaft bietet Zugriff auf , und alles andere, was HttpContext von RouteData MVC und Pages bereitgestellt Razor wird.

Die Verwendung der Resource -Eigenschaft ist frameworkspezifisch. Die Verwendung von Informationen in der Resource -Eigenschaft schränkt Ihre Autorisierungsrichtlinien auf bestimmte Frameworks ein. Sie sollten die Eigenschaft mithilfe des Schlüsselworts umleiten Resource is und dann bestätigen, dass die Umwandlung erfolgreich war, um sicherzustellen, dass Ihr Code nicht mit einem abstürzt, InvalidCastException wenn er in anderen Frameworks ausgeführt wird:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Globales Anfordern der Authentifizierung aller Benutzer

Informationen dazu, wie global die Authentifizierung aller Benutzer angefordert werden kann, finden Sie unter Authentifizierte Benutzer erforderlich.

Im Weiteren verwenden die rollenbasierte Autorisierung und die anspruchsbasierte Autorisierung eine Anforderung, einen Anforderungshandler und eine vorkonfigurierte Richtlinie. Diese Bausteine unterstützen den Ausdruck von Autorisierungsauswertungen im Code. Das Ergebnis ist eine umfangreichere, wiederverwendbare, testbare Autorisierungsstruktur.

Eine Autorisierungsrichtlinie besteht aus mindestens einer Anforderung. Sie wird als Teil der Konfiguration des Autorisierungsdiensts in der Startup.ConfigureServices -Methode registriert:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

Im vorherigen Beispiel wird eine Richtlinie "AtLeast21" erstellt. Es besteht eine einzelne Anforderung — eines Mindestalters, das als Parameter für die Anforderung bereitgestellt wird.

IAuthorizationService

Der primäre Dienst, der bestimmt, ob die Autorisierung erfolgreich war, ist IAuthorizationService :

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Im vorangehenden Code werden die beiden Methoden von IAuthorizationServicehervorgehoben.

IAuthorizationRequirement ist ein Markerdienst ohne Methoden und der Mechanismus zum Nachverfolgen, ob die Autorisierung erfolgreich ist.

Jeder IAuthorizationHandler ist für die Überprüfung verantwortlich, ob die Anforderungen erfüllt sind:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Die AuthorizationHandlerContext -Klasse wird vom Handler verwendet, um zu kennzeichnen, ob die Anforderungen erfüllt wurden:

 context.Succeed(requirement)

Der folgende Code zeigt die vereinfachte (und mit Kommentaren versehene) Standardimplementierungen des Autorisierungsdiensts:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

Der folgende Code zeigt ein typisches ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

Verwenden Sie IAuthorizationService oder [Authorize(Policy = "Something")] für die Autorisierung.

Anwenden von Richtlinien auf MVC-Controller

Wenn Sie Razor Pages verwenden, finden Sie weitere Informationen unter Anwenden von Richtlinien auf Razor Seiten in diesem Dokument.

Die Richtlinien werden auf den Controller angewendet, indem das [Authorize]-Attribut mit dem Richtliniennamen verwendet wird. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

Anwenden von Richtlinien auf Razor Seiten

Richtlinien werden auf Pages angewendet, Razor indem das [Authorize] -Attribut mit dem Richtliniennamen verwendet wird. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

Richtlinien können nicht auf Razor Seitenhandlerebene angewendet werden, sie müssen auf die Seite angewendet werden.

Richtlinien können Razor mithilfe einer Autorisierungskonventionauf Pages angewendet werden.

Anforderungen

Eine Autorisierungsanforderung ist eine Sammlung von Datenparametern, die eine Richtlinie zum Auswerten des aktuellen Benutzerprinzipals verwenden kann. In unserer Richtlinie "AtLeast21" ist die Anforderung ein einzelner Parameter — für das Mindestalter. Eine Anforderung implementiert IAuthorizationRequirement,eine leere Markerschnittstelle. Eine parametrisierte Mindestaltersanforderung kann wie folgt implementiert werden:

using Microsoft.AspNetCore.Authorization;

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

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

Wenn eine Autorisierungsrichtlinie mehrere Autorisierungsanforderungen enthält, müssen alle Anforderungen erfüllt sein, damit die Richtlinienauswertung erfolgreich ist. Anders ausgedrückt: Mehrere Autorisierungsanforderungen, die einer einzelnen Autorisierungsrichtlinie hinzugefügt werden, werden auf AND-Basis behandelt.

Hinweis

Eine Anforderung erfordert keine Daten oder Eigenschaften.

Autorisierungshandler

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

Eine Anforderung kann mehrere Handler aufweisen. Ein Handler kann AuthorizationHandler <TRequirement> erben,wobei TRequirement die Zu behandelnde Anforderung ist. Alternativ kann ein Handler IAuthorizationHandler implementieren, um mehr als einen Anforderungstyp zu verarbeiten.

Verwenden eines Handlers für eine Anforderung

Es folgt ein Beispiel für eine 1:1-Beziehung, bei der ein Mindestaltershandler eine einzelne Anforderung nutzt:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: 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 && 
                                        c.Issuer == "http://contoso.com").Value);

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

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

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

Der vorangehende Code bestimmt, ob der aktuelle Benutzerprinzipal über ein Geburtsdatum verfügt, das von einem bekannten und vertrauenswürdigen Aussteller ausgestellt wurde. Die Autorisierung kann nicht 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 Benutzer das durch die Anforderung definierte Mindestalter erfüllt, wird die Autorisierung als erfolgreich eingestuft. Wenn die Autorisierung erfolgreich ist, context.Succeed wird mit der erfüllten Anforderung als einzigem Parameter aufgerufen.

Verwenden eines Handlers für mehrere Anforderungen

Im Folgenden finden Sie ein Beispiel für eine 1:n-Beziehung, in der ein Berechtigungshandler drei verschiedene Arten von Anforderungen verarbeiten kann:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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 vorangehende Code durchläuft PendingRequirements — eine Eigenschaft, die Anforderungen enthält, die nicht als erfolgreich markiert sind. Für eine ReadPermission Anforderung muss der Benutzer entweder Besitzer oder Sponsor sein, um auf die angeforderte Ressource zugreifen zu können. Im Fall einer EditPermission - oder DeletePermission -Anforderung muss er besitzer sein, um auf die angeforderte Ressource zugreifen zu können.

Handlerregistrierung

Handler werden während der Konfiguration in der Dienstsammlung registriert. Beispiel:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

Der vorangehende Code registriert MinimumAgeHandler sich als Singleton, indem er services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); aufruft. Handler können mit einer der integrierten Dienstlebensdauernregistriert werden.

Es ist möglich, sowohl eine Anforderung als auch einen Handler in einer einzelnen Klasse zu bündeln, die sowohl als auch IAuthorizationRequirement IAuthorizationHandler implementiert. Dies sorgt für eine enge Kopplung zwischen dem Handler und der Anforderung und wird nur für einfache Anforderungen und Handler empfohlen. Durch das Erstellen einer Klasse, die beide Schnittstellen implementiert, muss der Handler aufgrund des integrierten PassThroughAuthorizationHandler,der anforderungen selbst verarbeiten kann, nicht mehr in der Di registriert werden.

Ein gutes Beispiel finden Sie in der AssertionRequirement-Klasse, bei der AssertionRequirement sowohl eine Anforderung als auch der Handler in einer vollständig eigenständigen Klasse ist.

Was sollte ein Handler zurückgeben?

Beachten Sie, dass die Handle -Methode im Handlerbeispiel keinen Wert zurückgibt. Wie wird ein Status von Erfolg oder Fehler angezeigt?

  • Ein Handler gibt den Erfolg durch Aufrufen von an context.Succeed(IAuthorizationRequirement requirement) und übergibt die Anforderung, die erfolgreich überprüft wurde.

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

  • Rufen Sie auf, um einen Fehler zu gewährleisten, auch wenn andere Anforderungshandler erfolgreich context.Fail sind.

Wenn ein Handler context.Succeed oder aufruft, werden alle anderen Handler weiterhin context.Fail aufgerufen. Dadurch können Anforderungen Nebeneffekte erzeugen, z. B. die Protokollierung, die auch dann ausgeführt wird, wenn ein anderer Handler eine Anforderung erfolgreich überprüft oder fehlgeschlagen hat. Wenn diese Eigenschaft auf festgelegt ist, wird false die Ausführung von Handlern durch die InvokeHandlersAfterFailure-Eigenschaft kurzgeschaltet, wenn context.Fail aufgerufen wird. InvokeHandlersAfterFailure ist standardmäßig auf true , in diesem Fall werden alle Handler aufgerufen.

Hinweis

Autorisierungshandler werden aufgerufen, auch wenn die Authentifizierung fehlschlägt.

Warum sollte ich mehrere Handler für eine Anforderung benötigen?

In Fällen, in denen die Auswertung auf OR-Basis erfolgen soll, implementieren Sie mehrere Handler für eine einzelne Anforderung. Microsoft verfügt beispielsweise über Türen, die nur mit Schlüsselkarten geöffnet werden. Wenn Sie Ihre Schlüsselkarte zu Hause lassen, druckt der Empfangsleiter einen temporären Aufdruck und öffnet die Tür für Sie. In diesem Szenario haben Sie eine einzelne Anforderung, BuildingEntry, aber mehrere Handler, von denen jeder eine einzelne Anforderung untersucht.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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 registriertsind. Wenn einer der Handler erfolgreich ist, wenn eine Richtlinie das auswertet, BuildingEntryRequirement ist die Richtlinienauswertung erfolgreich.

Verwenden einer Funkt zum Erfüllen einer Richtlinie

Es kann Situationen geben, in denen die Erfüllung einer Richtlinie einfach im Code ausgedrückt werden kann. Beim Func<AuthorizationHandlerContext, bool> Konfigurieren Ihrer Richtlinie mit dem Richtlinien-Generator können Sie eine RequireAssertion bereitstellen.

Beispielsweise könnte der vorherige BadgeEntryHandler wie folgt umgeschrieben werden:

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

Zugreifen auf den MVC-Anforderungskontext in Handlern

Die HandleRequirementAsync Methode, die Sie in einem Autorisierungshandler implementieren, verfügt über zwei Parameter: und AuthorizationHandlerContext die , die Sie TRequirement behandeln. Frameworks wie MVC oder können der -Eigenschaft des SignalR -Objekts beliebige Objekte Resource AuthorizationHandlerContext hinzufügen, um zusätzliche Informationen zu übergeben.

Bei Verwendung des Endpunktroutings wird die Autorisierung in der Regel von der Autorisierungs-Middleware verarbeitet. In diesem Fall ist die Resource -Eigenschaft eine Instanz von HttpContext . Der Kontext kann für den Zugriff auf den aktuellen Endpunkt verwendet werden, der zum Untersuchen der zugrunde liegenden Ressource verwendet werden kann, an die Sie das Routing durchführen. Beispiel:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

Beim herkömmlichen Routing oder wenn die Autorisierung als Teil des Autorisierungsfilters von MVC erfolgt, ist der Wert von Resource eine AuthorizationFilterContext -Instanz. Diese Eigenschaft bietet Zugriff auf , und alles andere, was HttpContext von RouteData MVC und Pages bereitgestellt Razor wird.

Die Verwendung der Resource -Eigenschaft ist frameworkspezifisch. Die Verwendung von Informationen in der Resource -Eigenschaft schränkt Ihre Autorisierungsrichtlinien auf bestimmte Frameworks ein. Sie sollten die Eigenschaft mithilfe des Schlüsselworts umleiten Resource is und dann bestätigen, dass die Umwandlung erfolgreich war, um sicherzustellen, dass Ihr Code nicht mit einem abstürzt, InvalidCastException wenn er in anderen Frameworks ausgeführt wird:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Globales Anfordern der Authentifizierung aller Benutzer

Informationen dazu, wie global die Authentifizierung aller Benutzer angefordert werden kann, finden Sie unter Authentifizierte Benutzer erforderlich.

Im Weiteren verwenden die rollenbasierte Autorisierung und die anspruchsbasierte Autorisierung eine Anforderung, einen Anforderungshandler und eine vorkonfigurierte Richtlinie. Diese Bausteine unterstützen den Ausdruck von Autorisierungsauswertungen im Code. Das Ergebnis ist eine umfangreichere, wiederverwendbare, testbare Autorisierungsstruktur.

Eine Autorisierungsrichtlinie besteht aus mindestens einer Anforderung. Sie wird als Teil der Konfiguration des Autorisierungsdiensts in der Startup.ConfigureServices -Methode registriert:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

Im vorherigen Beispiel wird eine Richtlinie "AtLeast21" erstellt. Es besteht eine einzelne Anforderung — eines Mindestalters, das als Parameter für die Anforderung bereitgestellt wird.

IAuthorizationService

Der primäre Dienst, der bestimmt, ob die Autorisierung erfolgreich war, ist IAuthorizationService :

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Im vorangehenden Code werden die beiden Methoden von IAuthorizationServicehervorgehoben.

IAuthorizationRequirement ist ein Markerdienst ohne Methoden und der Mechanismus zum Nachverfolgen, ob die Autorisierung erfolgreich ist.

Jeder IAuthorizationHandler ist für die Überprüfung verantwortlich, ob die Anforderungen erfüllt sind:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Die AuthorizationHandlerContext -Klasse wird vom Handler verwendet, um zu kennzeichnen, ob die Anforderungen erfüllt wurden:

 context.Succeed(requirement)

Der folgende Code zeigt die vereinfachte (und mit Kommentaren versehene) Standardimplementierungen des Autorisierungsdiensts:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

Der folgende Code zeigt ein typisches ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

Verwenden Sie IAuthorizationService oder [Authorize(Policy = "Something")] für die Autorisierung.

Anwenden von Richtlinien auf MVC-Controller

Wenn Sie Razor Pages verwenden, finden Sie weitere Informationen unter Anwenden von Richtlinien auf Razor Seiten in diesem Dokument.

Die Richtlinien werden auf den Controller angewendet, indem das [Authorize]-Attribut mit dem Richtliniennamen verwendet wird. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

Anwenden von Richtlinien auf Razor Seiten

Richtlinien werden auf Pages angewendet, Razor indem das [Authorize] -Attribut mit dem Richtliniennamen verwendet wird. Beispiel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

Richtlinien können nicht auf Razor Seitenhandlerebene angewendet werden, sie müssen auf die Seite angewendet werden.

Richtlinien können Razor mithilfe einer Autorisierungskonventionauf Pages angewendet werden.

Anforderungen

Eine Autorisierungsanforderung ist eine Sammlung von Datenparametern, die eine Richtlinie zum Auswerten des aktuellen Benutzerprinzipals verwenden kann. In unserer Richtlinie "AtLeast21" ist die Anforderung ein einzelner Parameter — für das Mindestalter. Eine Anforderung implementiert IAuthorizationRequirement,eine leere Markerschnittstelle. Eine parametrisierte Mindestaltersanforderung kann wie folgt implementiert werden:

using Microsoft.AspNetCore.Authorization;

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

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

Wenn eine Autorisierungsrichtlinie mehrere Autorisierungsanforderungen enthält, müssen alle Anforderungen erfüllt sein, damit die Richtlinienauswertung erfolgreich ist. Anders ausgedrückt: Mehrere Autorisierungsanforderungen, die einer einzelnen Autorisierungsrichtlinie hinzugefügt werden, werden auf AND-Basis behandelt.

Hinweis

Eine Anforderung erfordert keine Daten oder Eigenschaften.

Autorisierungshandler

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

Eine Anforderung kann mehrere Handler aufweisen. Ein Handler kann AuthorizationHandler <TRequirement> erben,wobei TRequirement die Zu behandelnde Anforderung ist. Alternativ kann ein Handler IAuthorizationHandler implementieren, um mehr als einen Anforderungstyp zu verarbeiten.

Verwenden eines Handlers für eine Anforderung

Es folgt ein Beispiel für eine 1:1-Beziehung, bei der ein Mindestaltershandler eine einzelne Anforderung nutzt:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: 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 && 
                                        c.Issuer == "http://contoso.com").Value);

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

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

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

Der vorangehende Code bestimmt, ob der aktuelle Benutzerprinzipal über ein Geburtsdatum verfügt, das von einem bekannten und vertrauenswürdigen Aussteller ausgestellt wurde. Die Autorisierung kann nicht 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 Benutzer das durch die Anforderung definierte Mindestalter erfüllt, wird die Autorisierung als erfolgreich eingestuft. Wenn die Autorisierung erfolgreich ist, context.Succeed wird mit der erfüllten Anforderung als einzigem Parameter aufgerufen.

Verwenden eines Handlers für mehrere Anforderungen

Im Folgenden finden Sie ein Beispiel für eine 1:n-Beziehung, in der ein Berechtigungshandler drei verschiedene Arten von Anforderungen verarbeiten kann:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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 vorangehende Code durchläuft PendingRequirements — eine Eigenschaft, die Anforderungen enthält, die nicht als erfolgreich markiert sind. Für eine ReadPermission Anforderung muss der Benutzer entweder Besitzer oder Sponsor sein, um auf die angeforderte Ressource zugreifen zu können. Im Fall einer EditPermission - oder DeletePermission -Anforderung muss er besitzer sein, um auf die angeforderte Ressource zugreifen zu können.

Handlerregistrierung

Handler werden während der Konfiguration in der Dienstsammlung registriert. Beispiel:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

Der vorangehende Code registriert MinimumAgeHandler sich als Singleton, indem er services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); aufruft. Handler können mit einer der integrierten Dienstlebensdauernregistriert werden.

Was sollte ein Handler zurückgeben?

Beachten Sie, dass die Handle -Methode im Handlerbeispiel keinen Wert zurückgibt. Wie wird ein Status von Erfolg oder Fehler angezeigt?

  • Ein Handler gibt den Erfolg durch Aufrufen von an context.Succeed(IAuthorizationRequirement requirement) und übergibt die Anforderung, die erfolgreich überprüft wurde.

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

  • Rufen Sie auf, um einen Fehler zu gewährleisten, auch wenn andere Anforderungshandler erfolgreich context.Fail sind.

Wenn ein Handler context.Succeed oder aufruft, werden alle anderen Handler weiterhin context.Fail aufgerufen. Dadurch können Anforderungen Nebeneffekte erzeugen, z. B. die Protokollierung, die auch dann ausgeführt wird, wenn ein anderer Handler eine Anforderung erfolgreich überprüft oder fehlgeschlagen hat. Wenn diese Eigenschaft auf festgelegt ist, wird false die Ausführung von Handlern durch die InvokeHandlersAfterFailure-Eigenschaft kurzgeschaltet, wenn context.Fail aufgerufen wird. InvokeHandlersAfterFailure ist standardmäßig auf true , in diesem Fall werden alle Handler aufgerufen.

Hinweis

Autorisierungshandler werden aufgerufen, auch wenn die Authentifizierung fehlschlägt.

Warum sollte ich mehrere Handler für eine Anforderung benötigen?

In Fällen, in denen die Auswertung auf OR-Basis erfolgen soll, implementieren Sie mehrere Handler für eine einzelne Anforderung. Microsoft verfügt beispielsweise über Türen, die nur mit Schlüsselkarten geöffnet werden. Wenn Sie Ihre Schlüsselkarte zu Hause lassen, druckt der Empfangsleiter einen temporären Aufdruck und öffnet die Tür für Sie. In diesem Szenario haben Sie eine einzelne Anforderung, BuildingEntry, aber mehrere Handler, von denen jeder eine einzelne Anforderung untersucht.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

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

        //TODO: 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 registriertsind. Wenn einer der Handler erfolgreich ist, wenn eine Richtlinie das auswertet, BuildingEntryRequirement ist die Richtlinienauswertung erfolgreich.

Verwenden einer Funkt zum Erfüllen einer Richtlinie

Es kann Situationen geben, in denen die Erfüllung einer Richtlinie einfach im Code ausgedrückt werden kann. Beim Func<AuthorizationHandlerContext, bool> Konfigurieren Ihrer Richtlinie mit dem Richtlinien-Generator können Sie eine RequireAssertion bereitstellen.

Beispielsweise könnte der vorherige BadgeEntryHandler wie folgt umgeschrieben werden:

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

Zugreifen auf den MVC-Anforderungskontext in Handlern

Die HandleRequirementAsync Methode, die Sie in einem Autorisierungshandler implementieren, verfügt über zwei Parameter: und AuthorizationHandlerContext die , die Sie TRequirement behandeln. Frameworks wie MVC oder können der -Eigenschaft des SignalR -Objekts beliebige Objekte Resource AuthorizationHandlerContext hinzufügen, um zusätzliche Informationen zu übergeben.

Bei Verwendung des Endpunktroutings wird die Autorisierung in der Regel von der Autorisierungs-Middleware verarbeitet. In diesem Fall ist die Resource -Eigenschaft eine Instanz von Endpoint . Der Endpunkt kann verwendet werden, um die zugrunde liegende Ressource zu untersuchen, an die Sie weiterleiten. Beispiel:

if (context.Resource is Endpoint endpoint)
{
   var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
   ...
}

Der Endpunkt bietet keinen Zugriff auf die aktuelle HttpContext . Wenn Sie Endpunktrouting verwenden, verwenden IHttpContextAcessor Sie , um innerhalb eines HttpContext Autorisierungshandlers auf zuzugreifen. Weitere Informationen finden Sie unter Verwenden von HttpContext aus benutzerdefinierten Komponenten.

Beim herkömmlichen Routing oder wenn die Autorisierung als Teil des Autorisierungsfilters von MVC erfolgt, ist der Wert von Resource eine AuthorizationFilterContext -Instanz. Diese Eigenschaft bietet Zugriff auf , und alles andere, was HttpContext von RouteData MVC und Pages bereitgestellt Razor wird.

Die Verwendung der Resource -Eigenschaft ist frameworkspezifisch. Die Verwendung von Informationen in der Resource -Eigenschaft schränkt Ihre Autorisierungsrichtlinien auf bestimmte Frameworks ein. Sie sollten die Eigenschaft mithilfe des Schlüsselworts umleiten Resource is und dann bestätigen, dass die Umwandlung erfolgreich war, um sicherzustellen, dass Ihr Code nicht mit einem abstürzt, InvalidCastException wenn er in anderen Frameworks ausgeführt wird:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Globales Anfordern der Authentifizierung aller Benutzer

Informationen dazu, wie global die Authentifizierung aller Benutzer angefordert werden kann, finden Sie unter Authentifizierte Benutzer erforderlich.