Autorisation basée sur une stratégie dans ASP.NET Core

En dessous des couvertures, l’autorisation basée sur les rôles et l’autorisation baséesur les revendications utilisent une exigence, un gestionnaire de conditions requises et une stratégie préconfigurée. Ces blocs de construction prennent en charge l’expression des évaluations d’autorisation dans le code. Le résultat est une structure d’autorisation plus riche, réutilisable et testable.

Une stratégie d’autorisation se compose d’une ou plusieurs exigences. Inscrivez-le dans le cadre de la configuration du service d’autorisation, dans le fichier de l’application Program.cs :

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

Dans l’exemple précédent, une stratégie « AtLeast21 » est créée. Il a une exigence unique : celle d’un âge minimal, qui est fourni en tant que paramètre à l’exigence.

IAuthorizationService

Le service principal qui détermine si l’autorisation réussit est 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);
}

Le code précédent met en évidence les deux méthodes du IAuthorizationService.

IAuthorizationRequirement est un service de marqueur sans méthode et le mécanisme de suivi de la réussite de l’autorisation.

Chacun IAuthorizationHandler d’eux est chargé de vérifier si les exigences sont remplies :

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

La AuthorizationHandlerContext classe est ce que le gestionnaire utilise pour marquer si les exigences ont été remplies :

 context.Succeed(requirement)

Le code suivant montre l’implémentation par défaut simplifiée (et annotée avec des commentaires) du service d’autorisation :

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

Le code suivant montre une configuration de service d’autorisation classique :

// 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")));

Utilisez IAuthorizationService, [Authorize(Policy = "Something")]ou RequireAuthorization("Something") pour l’autorisation.

Appliquer des stratégies aux contrôleurs MVC

Pour les applications qui utilisent Razor Des pages, consultez la section Appliquer des stratégies aux Razor pages .

Appliquez des stratégies aux contrôleurs à l’aide de l’attribut [Authorize] avec le nom de la stratégie. Par exemple :

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

namespace AuthorizationPoliciesSample.Controllers;

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

Appliquer des stratégies aux Razor pages

Appliquez des stratégies aux pages à Razor l’aide de l’attribut [Authorize] avec le nom de la stratégie. Par exemple :

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

namespace AuthorizationPoliciesSample.Pages;

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

Les stratégies ne peuvent pas être appliquées au niveau du Razor gestionnaire de pages, elles doivent être appliquées à la page.

Les stratégies peuvent également être appliquées aux pages à Razor l’aide d’une convention d’autorisation.

Appliquer des stratégies aux points de terminaison

Appliquez des stratégies aux points de terminaison à l’aide RequireAuthorization du nom de la stratégie. Par exemple :

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

Spécifications

Une exigence d’autorisation est une collection de paramètres de données qu’une stratégie peut utiliser pour évaluer le principal d’utilisateur actuel. Dans notre stratégie « AtLeast21 », l’exigence est un paramètre unique , l’âge minimal. Une exigence implémente IAuthorizationRequirement, qui est une interface de marqueur vide. Une exigence d’âge minimum paramétrable peut être implémentée comme suit :

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

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

    public int MinimumAge { get; }
}

Si une stratégie d’autorisation contient plusieurs exigences d’autorisation, toutes les exigences doivent être passées pour que l’évaluation de la stratégie réussisse. En d’autres termes, plusieurs exigences d’autorisation ajoutées à une stratégie d’autorisation unique sont traitées sur une base AND .

Notes

Une exigence n’a pas besoin d’avoir des données ou des propriétés.

Gestionnaires d’autorisation

Un gestionnaire d’autorisation est responsable de l’évaluation des propriétés d’une exigence. Le gestionnaire d’autorisation évalue les exigences par rapport à une condition fournie AuthorizationHandlerContext pour déterminer si l’accès est autorisé.

Une exigence peut avoir plusieurs gestionnaires. Un gestionnaire peut hériter AuthorizationHandler<TRequirement>, où TRequirement est la nécessité d’être géré. Vous pouvez également implémenter IAuthorizationHandler un gestionnaire directement pour gérer plusieurs types d’exigences.

Utiliser un gestionnaire pour une exigence

L’exemple suivant montre une relation un-à-un dans laquelle un gestionnaire d’âge minimal gère une exigence unique :

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

Le code précédent détermine si le principal d’utilisateur actuel a une revendication de date de naissance émise par un émetteur connu et approuvé. L’autorisation ne peut pas se produire lorsque la revendication est manquante, auquel cas une tâche terminée est retournée. Lorsqu’une revendication est présente, l’âge de l’utilisateur est calculé. Si l’utilisateur répond à l’âge minimal défini par l’exigence, l’autorisation est considérée comme réussie. Lorsque l’autorisation réussit, context.Succeed elle est appelée avec l’exigence satisfaite comme seul paramètre.

Utiliser un gestionnaire pour plusieurs exigences

L’exemple suivant montre une relation un-à-plusieurs dans laquelle un gestionnaire d’autorisations peut gérer trois types d’exigences différents :

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

Le code précédent traverse PendingRequirementsune propriété contenant les exigences non marquées comme réussies. Pour une ReadPermission exigence, l’utilisateur doit être propriétaire ou sponsor pour accéder à la ressource demandée. Pour une EditPermission ou DeletePermission une exigence, il doit s’agir d’un propriétaire pour accéder à la ressource demandée.

Inscription du gestionnaire

Inscrivez des gestionnaires dans la collection de services pendant la configuration. Par exemple :

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

Le code précédent s’inscrit MinimumAgeHandler en tant que singleton. Les gestionnaires peuvent être inscrits à l’aide de l’une des durées de vie de service intégrées.

Il est possible de regrouper à la fois une exigence et un gestionnaire dans une seule classe implémentant les deux IAuthorizationRequirement et IAuthorizationHandler. Ce regroupement crée un couplage serré entre le gestionnaire et l’exigence et est recommandé uniquement pour les exigences et les gestionnaires simples. La création d’une classe qui implémente les deux interfaces supprime la nécessité d’inscrire le gestionnaire dans l’interface di en raison du gestionnaire intégré PassThroughAuthorizationHandler qui permet de gérer eux-mêmes les exigences.

Consultez la classe AssertionRequirement pour obtenir un bon exemple dans lequel il s’agit à la AssertionRequirement fois d’une exigence et du gestionnaire dans une classe entièrement autonome.

Qu’est-ce qu’un gestionnaire doit retourner ?

Notez que la Handle méthode de l’exemple de gestionnaire ne retourne aucune valeur. Comment un état de réussite ou d’échec est-il indiqué ?

  • Un gestionnaire indique la réussite en appelant context.Succeed(IAuthorizationRequirement requirement), en transmettant l’exigence qui a été validée avec succès.

  • Un gestionnaire n’a généralement pas besoin de gérer les défaillances, car d’autres gestionnaires pour la même exigence peuvent réussir.

  • Pour garantir l’échec, même si d’autres gestionnaires d’exigences réussissent, appelez context.Fail.

Si un gestionnaire appelle context.Succeed ou context.Fail, tous les autres gestionnaires sont toujours appelés. Cela permet aux exigences de produire des effets secondaires, tels que la journalisation, qui a lieu même si un autre gestionnaire a correctement validé ou échoué une exigence. Lorsqu’elle est définie falsesur , la InvokeHandlersAfterFailure propriété court-circuite l’exécution des gestionnaires lorsqu’elle context.Fail est appelée. InvokeHandlersAfterFailure la valeur par défaut est true, auquel cas tous les gestionnaires sont appelés.

Notes

Les gestionnaires d’autorisation sont appelés même en cas d’échec de l’authentification. Par ailleurs, les gestionnaires peuvent s’exécuter dans n’importe quel ordre. Ils ne dépendent donc pas d’eux qui sont appelés dans un ordre particulier.

Pourquoi voulez-vous que plusieurs gestionnaires soient requis ?

Dans les cas où vous souhaitez que l’évaluation soit sur une base OR , implémentez plusieurs gestionnaires pour une seule exigence. Par exemple, Microsoft a des portes qui s’ouvrent uniquement avec des cartes de clés. Si vous laissez votre carte de clé à la maison, le réceptionniste imprime un autocollant temporaire et ouvre la porte pour vous. Dans ce scénario, vous avez une seule exigence, BuildingEntry, mais plusieurs gestionnaires, chacun examinant une seule exigence.

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

Vérifiez que les deux gestionnaires sont inscrits. Si l’un des gestionnaires réussit lorsqu’une stratégie évalue le BuildingEntryRequirement, l’évaluation de la stratégie réussit.

Utiliser un func pour répondre à une stratégie

Il peut y avoir des situations dans lesquelles l’exécution d’une stratégie est simple à exprimer dans le code. Il est possible de fournir une Func<AuthorizationHandlerContext, bool> stratégie lors de la configuration d’une stratégie avec le RequireAssertion générateur de stratégies.

Par exemple, le précédent BadgeEntryHandler peut être réécrit comme suit :

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

Accéder au contexte de requête MVC dans les gestionnaires

La HandleRequirementAsync méthode a deux paramètres : un AuthorizationHandlerContext et le TRequirement géré. Les frameworks tels que MVC ou SignalR sont libres d’ajouter n’importe quel objet à la Resource propriété sur la AuthorizationHandlerContext base de données pour transmettre des informations supplémentaires.

Lors de l’utilisation du routage de point de terminaison, l’autorisation est généralement gérée par l’intergiciel d’autorisation. Dans ce cas, la Resource propriété est une instance de HttpContext. Le contexte peut être utilisé pour accéder au point de terminaison actuel, qui peut être utilisé pour sonder la ressource sous-jacente vers laquelle vous effectuez le routage. Par exemple :

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

Avec le routage traditionnel ou lorsque l’autorisation se produit dans le cadre du filtre d’autorisation de MVC, la valeur d’une Resource instance est une AuthorizationFilterContext instance. Cette propriété fournit l’accès HttpContextà , et RouteDatatout le reste fourni par MVC et Razor Pages.

L’utilisation de la propriété est spécifique à l’infrastructure Resource . L’utilisation d’informations dans la propriété limite vos stratégies d’autorisation Resource à des infrastructures particulières. Castez la propriété à l’aide du Resourceis mot clé, puis vérifiez que le cast a réussi à garantir que votre code ne se bloque pas avec une InvalidCastException exécution sur d’autres frameworks :

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

À l’échelle mondiale, tous les utilisateurs doivent être authentifiés

Pour plus d’informations sur la façon de demander globalement à tous les utilisateurs d’être authentifiés, consultez Exiger des utilisateurs authentifiés.

Sous les couvertures, l’autorisation basée sur les rôles et l’autorisation baséesur les revendications utilisent une exigence, un gestionnaire de conditions requises et une stratégie préconfigurée. Ces blocs de construction prennent en charge l’expression des évaluations d’autorisation dans le code. Le résultat est une structure d’autorisation plus riche, réutilisable et testable.

Une stratégie d’autorisation se compose d’une ou plusieurs exigences. Il est inscrit dans le cadre de la configuration du service d’autorisation, dans la Startup.ConfigureServices méthode :

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

Dans l’exemple précédent, une stratégie « AtLeast21 » est créée. Il a une exigence unique : celle d’un âge minimal, qui est fourni en tant que paramètre à l’exigence.

IAuthorizationService

Le service principal qui détermine si l’autorisation réussit est 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);
}

Le code précédent met en évidence les deux méthodes du IAuthorizationService.

IAuthorizationRequirement est un service de marqueur sans méthode et le mécanisme de suivi de la réussite de l’autorisation.

Chacune IAuthorizationHandler est chargée de vérifier si les exigences sont remplies :

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

La AuthorizationHandlerContext classe est ce que le gestionnaire utilise pour marquer si les exigences ont été remplies :

 context.Succeed(requirement)

Le code suivant montre l’implémentation par défaut simplifiée (et annotée avec des commentaires) du service d’autorisation :

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

Le code suivant montre une caractéristique :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();
}

Utilisez ou [Authorize(Policy = "Something")] pour l’autorisationIAuthorizationService.

Appliquer des stratégies aux contrôleurs MVC

Si vous utilisez Razor Des pages, consultez Appliquer des stratégies aux Razor pages dans ce document.

Les stratégies sont appliquées aux contrôleurs à l’aide de l'attribut [Authorize] avec le nom de la stratégie. Par exemple :

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

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

Appliquer des stratégies aux Razor pages

Les stratégies sont appliquées aux pages à Razor l’aide de l’attribut avec le [Authorize] nom de la stratégie. Par exemple :

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

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

Les stratégies ne peuvent pas être appliquées au Razor niveau du gestionnaire de pages, elles doivent être appliquées à la page.

Les stratégies peuvent être appliquées aux pages à Razor l’aide d’une convention d’autorisation.

Spécifications

Une exigence d’autorisation est une collection de paramètres de données qu’une stratégie peut utiliser pour évaluer le principal de l’utilisateur actuel. Dans notre stratégie « AtLeast21 », l’exigence est un paramètre unique , l’âge minimal. Une exigence implémente IAuthorizationRequirement, qui est une interface de marqueur vide. Une exigence d’âge minimum paramétrable peut être implémentée comme suit :

using Microsoft.AspNetCore.Authorization;

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

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

Si une stratégie d’autorisation contient plusieurs exigences d’autorisation, toutes les exigences doivent passer pour que l’évaluation de la stratégie réussisse. En d’autres termes, plusieurs exigences d’autorisation ajoutées à une seule stratégie d’autorisation sont traitées sur une base AND .

Notes

Une exigence n’a pas besoin d’avoir des données ou des propriétés.

Gestionnaires d’autorisation

Un gestionnaire d’autorisation est responsable de l’évaluation des propriétés d’une exigence. Le gestionnaire d’autorisation évalue les exigences par rapport à une donnée AuthorizationHandlerContext pour déterminer si l’accès est autorisé.

Une exigence peut avoir plusieurs gestionnaires. Un gestionnaire peut hériter AuthorizationHandler<TRequirement>, où TRequirement est la nécessité d’être géré. Vous pouvez également implémenter IAuthorizationHandler un gestionnaire pour gérer plusieurs types d’exigences.

Utiliser un gestionnaire pour une exigence

L’exemple suivant montre une relation un-à-un dans laquelle un gestionnaire d’âge minimal utilise une exigence unique :

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

Le code précédent détermine si le principal d’utilisateur actuel a une revendication de naissance émise par un émetteur connu et approuvé. L’autorisation ne peut pas se produire lorsque la revendication est manquante, auquel cas une tâche terminée est retournée. Lorsqu’une revendication est présente, l’âge de l’utilisateur est calculé. Si l’utilisateur répond à l’âge minimal défini par l’exigence, l’autorisation est considérée comme réussie. Lorsque l’autorisation réussit, context.Succeed est appelée avec l’exigence satisfaite comme seul paramètre.

Utiliser un gestionnaire pour plusieurs exigences

L’exemple suivant montre une relation un-à-plusieurs dans laquelle un gestionnaire d’autorisations peut gérer trois types d’exigences différents :

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

Le code précédent traverse PendingRequirements: une propriété contenant les exigences non marquées comme réussies. Pour une ReadPermission exigence, l’utilisateur doit être propriétaire ou parrain pour accéder à la ressource demandée. Pour une EditPermission ou DeletePermission une exigence, l’utilisateur doit être propriétaire pour accéder à la ressource demandée.

Inscription du gestionnaire

Les gestionnaires sont inscrits dans la collection de services pendant la configuration. Par exemple :

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

Le code précédent s’inscrit MinimumAgeHandler en tant que singleton en appelant services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Les gestionnaires peuvent être inscrits à l’aide de l’une des durées de vie de service intégrées.

Il est possible de regrouper à la fois une exigence et un gestionnaire dans une seule classe implémentant les deux IAuthorizationRequirement et IAuthorizationHandler. Ce regroupement crée un couplage serré entre le gestionnaire et l’exigence et n’est recommandé que pour les exigences simples et les gestionnaires. La création d’une classe qui implémente les deux interfaces supprime la nécessité d’inscrire le gestionnaire dans LA di en raison du gestionnaire passThroughAuthorizationHandler intégré qui permet aux exigences de gérer elles-mêmes.

Consultez la classe AssertionRequirement pour obtenir un bon exemple où le AssertionRequirement composant est à la fois une exigence et le gestionnaire dans une classe autonome complète.

Qu’est-ce qu’un gestionnaire doit-il retourner ?

Notez que la Handle méthode de l’exemple de gestionnaire ne retourne aucune valeur. Comment un état de réussite ou d’échec est-il indiqué ?

  • Un gestionnaire indique la réussite en appelant context.Succeed(IAuthorizationRequirement requirement), en transmettant la condition qui a été validée avec succès.

  • Un gestionnaire n’a pas besoin de gérer les défaillances généralement, car d’autres gestionnaires pour la même exigence peuvent réussir.

  • Pour garantir l’échec, même si d’autres gestionnaires de conditions requises réussissent, appelez context.Fail.

Si un gestionnaire appelle context.Succeed ou context.Fail, tous les autres gestionnaires sont toujours appelés. Cela permet aux exigences de produire des effets secondaires, tels que la journalisation, qui se produit même si un autre gestionnaire a correctement validé ou échoué une exigence. Lorsqu’elle est définie falsesur , la InvokeHandlersAfterFailure propriété court-circuite l’exécution des gestionnaires lorsqu’elle context.Fail est appelée. InvokeHandlersAfterFailure par défaut , trueauquel cas tous les gestionnaires sont appelés.

Notes

Les gestionnaires d’autorisation sont appelés même si l’authentification échoue.

Pourquoi voulez-je plusieurs gestionnaires pour une exigence ?

Dans les cas où vous souhaitez que l’évaluation soit sur une base OR , implémentez plusieurs gestionnaires pour une exigence unique. Par exemple, Microsoft a des portes qui s’ouvrent uniquement avec des cartes de clé. Si vous laissez votre carte de clé à la maison, le réceptionniste imprime un autocollant temporaire et ouvre la porte pour vous. Dans ce scénario, vous avez une exigence unique, BuildingEntry, mais plusieurs gestionnaires, chacun examinant une exigence unique.

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

Vérifiez que les deux gestionnaires sont inscrits. Si l’un des gestionnaires réussit lorsqu’une stratégie évalue le BuildingEntryRequirement, l’évaluation de la stratégie réussit.

Utiliser un func pour remplir une stratégie

Il peut y avoir des situations dans lesquelles la réalisation d’une stratégie est simple à exprimer dans le code. Il est possible de fournir une Func<AuthorizationHandlerContext, bool> stratégie lors de la configuration de votre stratégie avec le RequireAssertion générateur de stratégies.

Par exemple, le précédent BadgeEntryHandler peut être réécrit comme suit :

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

Accéder au contexte de requête MVC dans les gestionnaires

La HandleRequirementAsync méthode que vous implémentez dans un gestionnaire d’autorisation a deux paramètres : un AuthorizationHandlerContext et celui TRequirement que vous gérez. Les frameworks tels que MVC ou SignalR sont libres d’ajouter n’importe quel objet à la Resource propriété pour AuthorizationHandlerContext transmettre des informations supplémentaires.

Lorsque vous utilisez le routage du point de terminaison, l’autorisation est généralement gérée par le middleware d’autorisation. Dans ce cas, la Resource propriété est une instance de HttpContext. Le contexte peut être utilisé pour accéder au point de terminaison actuel, qui peut être utilisé pour sonder la ressource sous-jacente vers laquelle vous effectuez le routage. Par exemple :

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

Avec le routage traditionnel, ou lorsque l’autorisation se produit dans le cadre du filtre d’autorisation de MVC, la valeur d’une Resource instance est une AuthorizationFilterContext instance. Cette propriété fournit l’accès HttpContextà , et RouteDatatout le reste fourni par MVC et Razor Pages.

L’utilisation de la propriété est spécifique à l’infrastructure Resource . L’utilisation d’informations dans la propriété limite vos stratégies d’autorisation Resource à des infrastructures particulières. Castez la propriété à l’aide du Resourceis mot clé, puis vérifiez que le cast a réussi à garantir que votre code ne se bloque pas avec une InvalidCastException exécution sur d’autres frameworks :

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

À l’échelle mondiale, tous les utilisateurs doivent être authentifiés

Pour plus d’informations sur la façon de demander globalement à tous les utilisateurs d’être authentifiés, consultez Exiger des utilisateurs authentifiés.

Sous les couvertures, l’autorisation basée sur les rôles et l’autorisation baséesur les revendications utilisent une exigence, un gestionnaire de conditions requises et une stratégie préconfigurée. Ces blocs de construction prennent en charge l’expression des évaluations d’autorisation dans le code. Le résultat est une structure d’autorisation plus riche, réutilisable et testable.

Une stratégie d’autorisation se compose d’une ou plusieurs exigences. Il est inscrit dans le cadre de la configuration du service d’autorisation, dans la Startup.ConfigureServices méthode :

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

Dans l’exemple précédent, une stratégie « AtLeast21 » est créée. Il a une exigence unique : celle d’un âge minimal, qui est fourni en tant que paramètre à l’exigence.

IAuthorizationService

Le service principal qui détermine si l’autorisation réussit est 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);
}

Le code précédent met en évidence les deux méthodes du IAuthorizationService.

IAuthorizationRequirement est un service de marqueur sans méthode et le mécanisme de suivi de la réussite de l’autorisation.

Chacune IAuthorizationHandler est chargée de vérifier si les exigences sont remplies :

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

La AuthorizationHandlerContext classe est ce que le gestionnaire utilise pour marquer si les exigences ont été remplies :

 context.Succeed(requirement)

Le code suivant montre l’implémentation par défaut simplifiée (et annotée avec des commentaires) du service d’autorisation :

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

Le code suivant montre une caractéristique :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();
}

Utilisez ou [Authorize(Policy = "Something")] pour l’autorisationIAuthorizationService.

Appliquer des stratégies aux contrôleurs MVC

Si vous utilisez Razor Des pages, consultez Appliquer des stratégies aux Razor pages dans ce document.

Les stratégies sont appliquées aux contrôleurs à l’aide de l'attribut [Authorize] avec le nom de la stratégie. Par exemple :

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

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

Appliquer des stratégies aux Razor pages

Les stratégies sont appliquées aux pages à Razor l’aide de l’attribut avec le [Authorize] nom de la stratégie. Par exemple :

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

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

Les stratégies ne peuvent pas être appliquées au Razor niveau du gestionnaire de pages, elles doivent être appliquées à la page.

Les stratégies peuvent être appliquées aux pages à Razor l’aide d’une convention d’autorisation.

Spécifications

Une exigence d’autorisation est une collection de paramètres de données qu’une stratégie peut utiliser pour évaluer le principal de l’utilisateur actuel. Dans notre stratégie « AtLeast21 », l’exigence est un paramètre unique , l’âge minimal. Une exigence implémente IAuthorizationRequirement, qui est une interface de marqueur vide. Une exigence d’âge minimum paramétrable peut être implémentée comme suit :

using Microsoft.AspNetCore.Authorization;

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

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

Si une stratégie d’autorisation contient plusieurs exigences d’autorisation, toutes les exigences doivent passer pour que l’évaluation de la stratégie réussisse. En d’autres termes, plusieurs exigences d’autorisation ajoutées à une seule stratégie d’autorisation sont traitées sur une base AND .

Notes

Une exigence n’a pas besoin d’avoir des données ou des propriétés.

Gestionnaires d’autorisation

Un gestionnaire d’autorisation est responsable de l’évaluation des propriétés d’une exigence. Le gestionnaire d’autorisation évalue les exigences par rapport à une donnée AuthorizationHandlerContext pour déterminer si l’accès est autorisé.

Une exigence peut avoir plusieurs gestionnaires. Un gestionnaire peut hériter AuthorizationHandler<TRequirement>, où TRequirement est la nécessité d’être géré. Vous pouvez également implémenter IAuthorizationHandler un gestionnaire pour gérer plusieurs types d’exigences.

Utiliser un gestionnaire pour une exigence

L’exemple suivant montre une relation un-à-un dans laquelle un gestionnaire d’âge minimal utilise une exigence unique :

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

Le code précédent détermine si le principal d’utilisateur actuel a une revendication de naissance émise par un émetteur connu et approuvé. L’autorisation ne peut pas se produire lorsque la revendication est manquante, auquel cas une tâche terminée est retournée. Lorsqu’une revendication est présente, l’âge de l’utilisateur est calculé. Si l’utilisateur répond à l’âge minimal défini par l’exigence, l’autorisation est considérée comme réussie. Lorsque l’autorisation réussit, context.Succeed est appelée avec l’exigence satisfaite comme seul paramètre.

Utiliser un gestionnaire pour plusieurs exigences

L’exemple suivant montre une relation un-à-plusieurs dans laquelle un gestionnaire d’autorisations peut gérer trois types d’exigences différents :

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

Le code précédent traverse PendingRequirements: une propriété contenant les exigences non marquées comme réussies. Pour une ReadPermission exigence, l’utilisateur doit être propriétaire ou parrain pour accéder à la ressource demandée. Pour une EditPermission ou DeletePermission une exigence, l’utilisateur doit être propriétaire pour accéder à la ressource demandée.

Inscription du gestionnaire

Les gestionnaires sont inscrits dans la collection de services pendant la configuration. Par exemple :

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

Le code précédent s’inscrit MinimumAgeHandler en tant que singleton en appelant services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Les gestionnaires peuvent être inscrits à l’aide de l’une des durées de vie de service intégrées.

Qu’est-ce qu’un gestionnaire doit-il retourner ?

Notez que la Handle méthode de l’exemple de gestionnaire ne retourne aucune valeur. Comment un état de réussite ou d’échec est-il indiqué ?

  • Un gestionnaire indique la réussite en appelant context.Succeed(IAuthorizationRequirement requirement), en transmettant la condition qui a été validée avec succès.

  • Un gestionnaire n’a pas besoin de gérer les défaillances généralement, car d’autres gestionnaires pour la même exigence peuvent réussir.

  • Pour garantir l’échec, même si d’autres gestionnaires de conditions requises réussissent, appelez context.Fail.

Si un gestionnaire appelle context.Succeed ou context.Fail, tous les autres gestionnaires sont toujours appelés. Cela permet aux exigences de produire des effets secondaires, tels que la journalisation, qui se produit même si un autre gestionnaire a correctement validé ou échoué une exigence. Lorsqu’elle est définie falsesur , la InvokeHandlersAfterFailure propriété court-circuite l’exécution des gestionnaires lorsqu’elle context.Fail est appelée. InvokeHandlersAfterFailure par défaut , trueauquel cas tous les gestionnaires sont appelés.

Notes

Les gestionnaires d’autorisation sont appelés même si l’authentification échoue.

Pourquoi voulez-je plusieurs gestionnaires pour une exigence ?

Dans les cas où vous souhaitez que l’évaluation soit sur une base OR , implémentez plusieurs gestionnaires pour une exigence unique. Par exemple, Microsoft a des portes qui s’ouvrent uniquement avec des cartes de clé. Si vous laissez votre carte de clé à la maison, le réceptionniste imprime un autocollant temporaire et ouvre la porte pour vous. Dans ce scénario, vous avez une exigence unique, BuildingEntry, mais plusieurs gestionnaires, chacun examinant une exigence unique.

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

Vérifiez que les deux gestionnaires sont inscrits. Si l’un des gestionnaires réussit lorsqu’une stratégie évalue le BuildingEntryRequirement, l’évaluation de la stratégie réussit.

Utiliser un func pour remplir une stratégie

Il peut y avoir des situations dans lesquelles la réalisation d’une stratégie est simple à exprimer dans le code. Il est possible de fournir une Func<AuthorizationHandlerContext, bool> stratégie lors de la configuration de votre stratégie avec le RequireAssertion générateur de stratégies.

Par exemple, le précédent BadgeEntryHandler peut être réécrit comme suit :

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

Accéder au contexte de requête MVC dans les gestionnaires

La HandleRequirementAsync méthode que vous implémentez dans un gestionnaire d’autorisation a deux paramètres : un AuthorizationHandlerContext et celui TRequirement que vous gérez. Les frameworks tels que MVC ou SignalR sont libres d’ajouter n’importe quel objet à la Resource propriété pour AuthorizationHandlerContext transmettre des informations supplémentaires.

Lorsque vous utilisez le routage du point de terminaison, l’autorisation est généralement gérée par le middleware d’autorisation. Dans ce cas, la Resource propriété est une instance de Endpoint. Le point de terminaison peut être utilisé pour sonder la ressource sous-jacente vers laquelle vous effectuez le routage. Par exemple :

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

Le point de terminaison ne fournit pas l’accès au point de terminaison actuel HttpContext. Lorsque vous utilisez le routage du point de terminaison, utilisez-le IHttpContextAccessor pour accéder HttpContext à l’intérieur d’un gestionnaire d’autorisation. Pour plus d’informations, consultez Utiliser HttpContext à partir de composants personnalisés.

Avec le routage traditionnel, ou lorsque l’autorisation se produit dans le cadre du filtre d’autorisation de MVC, la valeur d’une Resource instance est une AuthorizationFilterContext instance. Cette propriété fournit l’accès HttpContextà , et RouteDatatout le reste fourni par MVC et Razor Pages.

L’utilisation de la propriété est spécifique à l’infrastructure Resource . L’utilisation d’informations dans la propriété limite vos stratégies d’autorisation Resource à des infrastructures particulières. Castez la propriété à l’aide du Resourceis mot clé, puis vérifiez que le cast a réussi à garantir que votre code ne se bloque pas avec une InvalidCastException exécution sur d’autres frameworks :

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

À l’échelle mondiale, tous les utilisateurs doivent être authentifiés

Pour plus d’informations sur la façon de demander globalement à tous les utilisateurs d’être authentifiés, consultez Exiger des utilisateurs authentifiés.