Autorización basada en directivas en ASP.NET CorePolicy-based authorization in ASP.NET Core

Interiormente, autorización basada en roles y autorización basada en notificaciones usan un requisito, un controlador de requisito y una directiva configurada previamente.Underneath the covers, role-based authorization and claims-based authorization use a requirement, a requirement handler, and a pre-configured policy. Estos bloques de creación se admite la expresión de evaluaciones de autorización en el código.These building blocks support the expression of authorization evaluations in code. El resultado es una estructura de autorización completas, reutilizables y apta para las pruebas.The result is a richer, reusable, testable authorization structure.

Una directiva de autorización consta de uno o más requisitos.An authorization policy consists of one or more requirements. Está registrado como parte de la configuración del servicio de autorización, en el Startup.ConfigureServices método: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)));
    });
}

En el ejemplo anterior, se crea una directiva de "AtLeast21".In the preceding example, an "AtLeast21" policy is created. Tiene un requisito único—de una antigüedad mínima, que se suministra como parámetro al requisito.It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement.

Aplicar directivas a los controladores MVCApplying policies to MVC controllers

Si está usando las páginas de Razor, consulte aplicar directivas a las páginas de Razor en este documento.If you're using Razor Pages, see Applying policies to Razor Pages in this document.

Las directivas se aplican a los controladores mediante el uso de la [Authorize] atributo con el nombre de la directiva.Policies are applied to controllers by using the [Authorize] attribute with the policy name. Por ejemplo:For example:

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

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

Aplicar directivas a las páginas de RazorApplying policies to Razor Pages

Las directivas se aplican a las páginas de Razor mediante el uso de la [Authorize] atributo con el nombre de la directiva.Policies are applied to Razor Pages by using the [Authorize] attribute with the policy name. Por ejemplo:For example:

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

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

Las directivas pueden aplicarse también a las páginas de Razor mediante el uso de un convención autorización.Policies can also be applied to Razor Pages by using an authorization convention.

RequisitosRequirements

Un requisito de autorización es una colección de parámetros de datos que puede usar una directiva para evaluar la entidad de seguridad del usuario actual.An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. En nuestra directiva de "AtLeast21", el requisito es un único parámetro—la antigüedad mínima.In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. Implementa un requisito IAuthorizationRequirement, que es una interfaz de marcador vacío.A requirement implements IAuthorizationRequirement, which is an empty marker interface. Un requisito de antigüedad mínima con parámetros podría implementarse como sigue: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 una directiva de autorización contiene varios requisitos de autorización, deben pasar todos los requisitos para la evaluación de directivas se realice correctamente.If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. En otras palabras, se tratan varios requisitos de autorización que se agrega a una sola directiva de autorización en un AND base.In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

Nota

Un requisito no debe tener datos o propiedades.A requirement doesn't need to have data or properties.

Controladores de autorizaciónAuthorization handlers

Un controlador de autorización es responsable de la evaluación de propiedades de un requisito.An authorization handler is responsible for the evaluation of a requirement's properties. El controlador de autorización evalúa los requisitos en relación con proporcionado AuthorizationHandlerContext para determinar si se permite el acceso.The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

Puede tener un requisito varios controladores.A requirement can have multiple handlers. Puede heredar un controlador AuthorizationHandler<TRequirement >, donde TRequirement es el requisito para que lo administre.A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. Como alternativa, puede implementar un controlador IAuthorizationHandler para administrar más de un tipo de requisito.Alternatively, a handler may implement IAuthorizationHandler to handle more than one type of requirement.

Usar un controlador para uno de los requisitosUse a handler for one requirement

El siguiente es un ejemplo de una relación uno a uno en el que un controlador de antigüedad mínima utiliza un requisito único: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;
    }
}

El código anterior determina si la entidad de usuario actual tiene una fecha de nacimiento de notificación de que se ha emitido por un emisor conocido y de confianza.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. No se puede realizar la autorización cuando falta la notificación, en cuyo caso se devuelve una tarea completada.Authorization can't occur when the claim is missing, in which case a completed task is returned. Cuando hay una notificación, se calcula la edad del usuario.When a claim is present, the user's age is calculated. Si el usuario cumple la antigüedad mínima definida por el requisito, la autorización se considere correcta.If the user meets the minimum age defined by the requirement, authorization is deemed successful. Cuando se realiza correctamente, la autorización context.Succeed se invoca con el requisito satisfecho como su único parámetro.When authorization is successful, context.Succeed is invoked with the satisfied requirement as its sole parameter.

Usar un controlador para varios requisitosUse a handler for multiple requirements

El siguiente es un ejemplo de una relación uno a varios en el que un controlador de permiso puede controlar los tres tipos diferentes de los requisitos: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;
    }
}

El código anterior recorre PendingRequirements—una propiedad que contiene los requisitos no marcada como correcta.The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. Para un ReadPermission requisito, el usuario debe ser un propietario o un patrocinador para acceder al recurso solicitado.For a ReadPermission requirement, the user must be either an owner or a sponsor to access the requested resource. En el caso de un EditPermission o DeletePermission requisito quien debe ser un propietario para acceder al recurso solicitado.In the case of an EditPermission or DeletePermission requirement, he or she must be an owner to access the requested resource.

Registro del controladorHandler registration

Los controladores se registran en la colección de servicios durante la configuración.Handlers are registered in the services collection during configuration. Por ejemplo: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>();
}

El código anterior registra MinimumAgeHandler como un singleton invocando services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();.The preceding code registers MinimumAgeHandler as a singleton by invoking services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Los controladores pueden registrarse mediante cualquiera de los integrados duraciones de servicio.Handlers can be registered using any of the built-in service lifetimes.

¿Qué debe devolver un controlador?What should a handler return?

Tenga en cuenta que el Handle método en el ejemplo controlador no devuelve ningún valor.Note that the Handle method in the handler example returns no value. ¿Cómo es un estado de éxito o error indicado?How is a status of either success or failure indicated?

  • Un controlador indica éxito mediante una llamada a context.Succeed(IAuthorizationRequirement requirement), pasando el requisito de que se ha validado correctamente.A handler indicates success by calling context.Succeed(IAuthorizationRequirement requirement), passing the requirement that has been successfully validated.

  • Un controlador no tiene que controlar los errores por lo general, ya que otros controladores para el mismo requisito pueden ser correcto.A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

  • Para garantizar el error, incluso si otros controladores de requisitos se realice correctamente, llame a context.Fail.To guarantee failure, even if other requirement handlers succeed, call context.Fail.

Cuando se establece en false, InvokeHandlersAfterFailure propiedad (disponible en ASP.NET Core 1.1 y versiones posterior) provoca un cortocircuito en la ejecución de controladores cuando context.Fail se llama.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. InvokeHandlersAfterFailure el valor predeterminado es true, en cuyo caso se llama a todos los controladores.InvokeHandlersAfterFailure defaults to true, in which case all handlers are called. Esto permite que los requisitos producir efectos secundarios, como el registro, que siempre se produce incluso si context.Fail se ha llamado en otro controlador.This allows requirements to produce side effects, such as logging, which always take place even if context.Fail has been called in another handler.

¿Por qué querría varios controladores para un requisito?Why would I want multiple handlers for a requirement?

En los casos donde desee que la evaluación en un o base, implementar varios controladores para un requisito único.In cases where you want evaluation to be on an OR basis, implement multiple handlers for a single requirement. Por ejemplo, Microsoft tiene puertas que abre sólo con las tarjetas de clave.For example, Microsoft has doors which only open with key cards. Si deja la tarjeta de claves en casa, la recepcionista imprime un adhesivo temporal y abre la puerta.If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. En este escenario, tendría un requisito único, BuildingEntry, pero varios controladores, cada uno de ellos examinando un requisito único.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;
    }
}

Asegúrese de que ambos controladores estén registrado.Ensure that both handlers are registered. Si cualquier controlador se ejecuta correctamente cuando una directiva se evalúa como el BuildingEntryRequirement, la evaluación de directivas se realiza correctamente.If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

Uso de func para satisfacer una directivaUsing a func to fulfill a policy

Puede haber situaciones en que cumplir una directiva es sencilla expresar en código.There may be situations in which fulfilling a policy is simple to express in code. Es posible proporcionar un Func<AuthorizationHandlerContext, bool> al configurar la directiva con el RequireAssertion el generador de directiva.It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

Por ejemplo, el anterior BadgeEntryHandler podría volver a escribirse como sigue: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")));
});

Acceso al contexto de solicitud MVC en controladoresAccessing MVC request context in handlers

El HandleRequirementAsync método se implementa en un controlador de autorización tiene dos parámetros: un AuthorizationHandlerContext y TRequirement está controlando.The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. Marcos de trabajo como MVC o Jabbr son gratuitas agregar cualquier objeto a la Resource propiedad en el AuthorizationHandlerContext para pasar información adicional.Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.

Por ejemplo, MVC pasa una instancia de AuthorizationFilterContext en el Resource propiedad.For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. Esta propiedad proporciona acceso a HttpContext, RouteDatay todo lo contrario, proporcionada por MVC y páginas de Razor.This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

El uso de la Resource propiedad es el marco específico.The use of the Resource property is framework specific. Uso de la información la Resource propiedad limita sus directivas de autorización para marcos de trabajo determinados.Using information in the Resource property limits your authorization policies to particular frameworks. Primero debe convertir el Resource propiedad mediante la is palabra clave y, a continuación, confirme la conversión se realizó correctamente para asegurarse de que no se bloquee el código con un InvalidCastException cuando se ejecuta en otros marcos de trabajo: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.
}