Autorisation basée sur la stratégie dans ASP.NET CorePolicy-based authorization in ASP.NET Core

En coulisses, l’autorisation basée sur les rôles et l' autorisation basée sur les revendications utilisent une spécification, un gestionnaire de spécification et une stratégie préconfigurée.Underneath the covers, role-based authorization and claims-based authorization use a requirement, a requirement handler, and a pre-configured policy. Ces blocs de construction prennent en charge l’expression des évaluations d’autorisation dans le code.These building blocks support the expression of authorization evaluations in code. Le résultat est une structure d’autorisation plus riche, réutilisable et testable.The result is a richer, reusable, testable authorization structure.

Une stratégie d’autorisation se compose d’une ou de plusieurs exigences.An authorization policy consists of one or more requirements. Elle est inscrite dans le cadre de la configuration du service d' Startup.ConfigureServices autorisation, dans la méthode:It's registered as part of the authorization service configuration, in the Startup.ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    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.In the preceding example, an "AtLeast21" policy is created. Il a une exigence—unique qui correspond à une ancienneté minimale, qui est fournie en tant que paramètre à la spécification.It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement.

IAuthorizationServiceIAuthorizationService

Le service principal qui détermine si l’autorisation est réussie IAuthorizationServiceest le suivant:The primary service that determines if authorization is successful is 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 de IAuthorizationService.The preceding code highlights the two methods of the IAuthorizationService.

IAuthorizationRequirementest un service de marqueur sans méthode, et le mécanisme de suivi de la réussite de l’autorisation.IAuthorizationRequirement is a marker service with no methods, and the mechanism for tracking whether authorization is successful.

Chaque IAuthorizationHandler est responsable de la vérification de la satisfaction des exigences suivantes:Each IAuthorizationHandler is responsible for checking if requirements are met:

/// <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 spécifications ont été satisfaites:The AuthorizationHandlerContext class is what the handler uses to mark whether requirements have been met:

 context.Succeed(requirement)

Le code suivant illustre l’implémentation par défaut simplifiée (et annotée avec des commentaires) du service d’autorisation:The following code shows the simplified (and annotated with comments) default implementation of the authorization service:

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 illustre un exemple ConfigureServicestypique:The following code shows a typical 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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Utilisez IAuthorizationService ou[Authorize(Policy = "Something")] pour l’autorisation.Use IAuthorizationService or [Authorize(Policy = "Something")] for authorization.

Application de stratégies à des contrôleurs MVCApplying policies to MVC controllers

Si vous utilisez Razor Pages, consultez application de stratégies à Razor pages dans ce document.If you're using Razor Pages, see Applying policies to Razor Pages in this document.

Les stratégies sont appliquées aux contrôleurs à [Authorize] l’aide de l’attribut avec le nom de la stratégie.Policies are applied to controllers by using the [Authorize] attribute with the policy name. Par exemple :For example:

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

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

Application de stratégies à Razor PagesApplying policies to Razor Pages

Les stratégies sont appliquées à Razor pages à l' [Authorize] aide de l’attribut avec le nom de la stratégie.Policies are applied to Razor Pages by using the [Authorize] attribute with the policy name. Par exemple :For example:

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

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

Les stratégies peuvent également être appliquées à Razor Pages à l’aide d’une Convention d’autorisation.Policies can also be applied to Razor Pages by using an authorization convention.

Configuration requiseRequirements

Une spécification d’autorisation est une collection de paramètres de données qu’une stratégie peut utiliser pour évaluer le principal d’utilisateur actuel.An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. Dans notre stratégie «AtLeast21», l’exigence est un paramètre—unique de l’âge minimal.In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. Une spécification implémente IAuthorizationRequirement, qui est une interface de marqueur vide.A requirement implements IAuthorizationRequirement, which is an empty marker interface. Une exigence d’ancienneté minimale paramétrable peut être implémentée comme suit:A parameterized minimum age requirement could be implemented as follows:

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 spécifications doivent réussir pour que l’évaluation de la stratégie aboutisse.If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. En d’autres termes, plusieurs exigences d’autorisation ajoutées à une même stratégie d’autorisation sont traitées sur une base et .In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

Notes

Une exigence n’a pas besoin d’avoir des données ou des propriétés.A requirement doesn't need to have data or properties.

Gestionnaires d’autorisationsAuthorization handlers

Un gestionnaire d’autorisations est responsable de l’évaluation des propriétés d’une spécification.An authorization handler is responsible for the evaluation of a requirement's properties. Le gestionnaire d’autorisations évalue les spécifications par rapport à un AuthorizationHandlerContext fourni pour déterminer si l’accès est autorisé.The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

Une spécification peut avoir plusieurs gestionnaires.A requirement can have multiple handlers. Un gestionnaire peut hériter de <AuthorizationHandler TRequirement >, où TRequirement est la spécification à gérer.A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. Un gestionnaire peut également implémenter IAuthorizationHandler pour gérer plusieurs types d’exigences.Alternatively, a handler may implement IAuthorizationHandler to handle more than one type of requirement.

Utiliser un gestionnaire pour une spécificationUse a handler for one requirement

Voici un exemple de relation un-à-un dans laquelle un gestionnaire d’âge minimal utilise une seule exigence:The following is an example of a one-to-one relationship in which a minimum age handler utilizes a single requirement:

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 de l’utilisateur actuel possède une revendication de date de naissance qui a été émise par un émetteur connu et approuvé.The preceding code determines if the current user principal has a date of birth claim which has been issued by a known and trusted Issuer. L’autorisation ne peut pas se produire lorsque la revendication est manquante, auquel cas une tâche terminée est retournée.Authorization can't occur when the claim is missing, in which case a completed task is returned. Lorsqu’une revendication est présente, l’âge de l’utilisateur est calculé.When a claim is present, the user's age is calculated. Si l’utilisateur atteint l’ancienneté minimale définie par la spécification, l’autorisation est considérée comme réussie.If the user meets the minimum age defined by the requirement, authorization is deemed successful. Lorsque l’autorisation est réussie context.Succeed , est appelée avec la spécification satisfaite comme paramètre unique.When authorization is successful, context.Succeed is invoked with the satisfied requirement as its sole parameter.

Utiliser un gestionnaire pour plusieurs spécificationsUse a handler for multiple requirements

Voici un exemple de relation un-à-plusieurs dans laquelle un gestionnaire d’autorisations peut gérer trois types différents d’exigences:The following is an example of a one-to-many relationship in which a permission handler can handle three different types of requirements:

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 parcourt PendingRequirements—une propriété contenant des exigences non marquées comme ayant réussi.The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. Pour une ReadPermission spécification, l’utilisateur doit être un propriétaire ou un sponsor pour accéder à la ressource demandée.For a ReadPermission requirement, the user must be either an owner or a sponsor to access the requested resource. Dans le cas d’une EditPermission exigence DeletePermission ou, il doit être propriétaire de l’accès à la ressource demandée.In the case of an EditPermission or DeletePermission requirement, he or she must be an owner to access the requested resource.

Inscription du gestionnaireHandler registration

Les gestionnaires sont inscrits dans la collection de services pendant la configuration.Handlers are registered in the services collection during configuration. Par exemple :For example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    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>();.The preceding code registers MinimumAgeHandler as a singleton by invoking services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Les gestionnaires peuvent être inscrits à l’aide de l’une des durées de vie de serviceintégrées.Handlers can be registered using any of the built-in service lifetimes.

Que doit retourner un gestionnaire?What should a handler return?

Notez que la Handle méthode dans l' exemple de gestionnaire ne retourne aucune valeur.Note that the Handle method in the handler example returns no value. Comment l’état de la réussite ou de l’échec est-il indiqué?How is a status of either success or failure indicated?

  • Un gestionnaire indique la réussite en context.Succeed(IAuthorizationRequirement requirement)appelant, en passant la spécification qui a été validée avec succès.A handler indicates success by calling context.Succeed(IAuthorizationRequirement requirement), passing the requirement that has been successfully validated.

  • Un gestionnaire n’A pas besoin de gérer les défaillances en général, car d’autres gestionnaires pour la même spécification peuvent être exécutés correctement.A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

  • Pour garantir la défaillance, même si d’autres gestionnaires de spécifications sont context.Failcorrectement exécutés, appelez.To guarantee failure, even if other requirement handlers succeed, call context.Fail.

Si un gestionnaire appelle context.Succeed ou context.Fail, tous les autres gestionnaires sont toujours appelés.If a handler calls context.Succeed or context.Fail, all other handlers are still called. Cela permet aux exigences de produire des effets secondaires, tels que la journalisation, qui se produit même si un autre gestionnaire a validé ou échoué avec succès.This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. Quand la valeur falseest affectée à, la propriété InvokeHandlersAfterFailure (disponible dans ASP.net Core 1,1 et versions ultérieures) réduit les courts- context.Fail circuits de l’exécution des gestionnaires lorsque est appelé.When set to false, the InvokeHandlersAfterFailure property (available in ASP.NET Core 1.1 and later) short-circuits the execution of handlers when context.Fail is called. InvokeHandlersAfterFailurela valeur par défaut est ,auquelcastouslesgestionnairessontappelés.trueInvokeHandlersAfterFailure defaults to true, in which case all handlers are called.

Notes

Les gestionnaires d’autorisations sont appelés même si l’authentification échoue.Authorization handlers are called even if authentication fails.

Pourquoi dois-je utiliser plusieurs gestionnaires pour une spécification?Why would I want multiple handlers for a requirement?

Dans les cas où vous souhaitez que l’évaluation soit sur une base ou , implémentez plusieurs gestionnaires pour une seule exigence.In cases where you want evaluation to be on an OR basis, implement multiple handlers for a single requirement. Par exemple, Microsoft possède des portes qui s’ouvrent uniquement avec des cartes clés.For example, Microsoft has doors which only open with key cards. Si vous laissez votre carte clé chez vous, la réceptionniste imprime un autocollant temporaire et ouvre la porte pour vous.If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. Dans ce scénario, vous auriez besoin d’une seule exigence, BuildingEntry, mais plusieurs gestionnaires, chacun examinant une seule exigence.In this scenario, you'd have a single requirement, BuildingEntry, but multiple handlers, each one examining a single requirement.

BuildingEntryRequirement.csBuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.csBadgeEntryHandler.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.csTemporaryStickerHandler.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.Ensure that both handlers are registered. Si l’un des gestionnaires aboutit lorsqu’une stratégie évalue le BuildingEntryRequirement, l’évaluation de la stratégie aboutit.If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

Utilisation d’un Func pour accomplir une stratégieUsing a func to fulfill a policy

Il peut arriver que la réalisation d’une stratégie soit simple à exprimer dans le code.There may be situations in which fulfilling a policy is simple to express in code. Il est possible de fournir un Func<AuthorizationHandlerContext, bool> lors de la configuration de votre stratégie RequireAssertion avec le générateur de stratégie.It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

Par exemple, la précédente BadgeEntryHandler peut être réécrite comme suit:For example, the previous BadgeEntryHandler could be rewritten as follows:

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ès au contexte de requête MVC dans les gestionnairesAccessing MVC request context in handlers

La HandleRequirementAsync méthode que vous implémentez dans un gestionnaire d’autorisations a deux AuthorizationHandlerContext paramètres: TRequirement un et le que vous gérez.The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. Les infrastructures telles que MVC ou Jabbr sont libres d’ajouter n’importe quel objet Resource à la propriété AuthorizationHandlerContext sur le pour passer des informations supplémentaires.Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.

Par exemple, MVC passe une instance de AuthorizationFilterContext dans la Resource propriété.For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. Cette propriété fournit l’accès HttpContextà RouteData, et tout le reste fourni par MVC et Razor pages.This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

L’utilisation de la Resource propriété est spécifique à l’infrastructure.The use of the Resource property is framework specific. L’utilisation des informations Resource de la propriété limite vos stratégies d’autorisation à des infrastructures spécifiques.Using information in the Resource property limits your authorization policies to particular frameworks. Vous devez effectuer un Resource cast de la is propriété à l’aide du mot clé, puis confirmer que le cast a réussi pour s' InvalidCastException assurer que votre code ne se bloque pas avec un lorsqu’il est exécuté sur d’autres infrastructures:You should cast the Resource property using the is keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an InvalidCastException when run on other frameworks:

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