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

En segundo plano, la autorización basada en roles y la autorización basada en notificaciones usan un requisito, un controlador de requisitos 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 admiten 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 más enriquecida, reutilizable y comprobable.The result is a richer, reusable, testable authorization structure.

Una directiva de autorización se compone de uno o varios requisitos.An authorization policy consists of one or more requirements. Se registra como parte de la configuración del servicio de autorización, en el método Startup.ConfigureServices:It's registered as part of the authorization service configuration, in the Startup.ConfigureServices method:

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

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

IAuthorizationServiceIAuthorizationService

El servicio principal que determina si la autorización se realiza correctamente es IAuthorizationService: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);
}

En el código anterior se resaltan los dos métodos de IAuthorizationService.The preceding code highlights the two methods of the IAuthorizationService.

IAuthorizationRequirement es un servicio de marcador sin métodos y el mecanismo para hacer un seguimiento de si la autorización se realiza correctamente.IAuthorizationRequirement is a marker service with no methods, and the mechanism for tracking whether authorization is successful.

Cada IAuthorizationHandler es responsable de comprobar si se cumplen los requisitos: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 clase AuthorizationHandlerContext es lo que el controlador utiliza para indicar si se han cumplido los requisitos:The AuthorizationHandlerContext class is what the handler uses to mark whether requirements have been met:

 context.Succeed(requirement)

En el código siguiente se muestra la implementación predeterminada simplificada (y anotada con comentarios) del servicio de autorización: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);
}

En el código siguiente se muestra un ConfigureServicestípico: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.AddControllersWithViews();
    services.AddRazorPages();
}

Use IAuthorizationService o [Authorize(Policy = "Something")] para la autorización.Use IAuthorizationService or [Authorize(Policy = "Something")] for authorization.

Aplicar directivas a los controladores de MVCApplying policies to MVC controllers

Si utiliza Razor Pages, consulte aplicar directivas a Razor pages 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 atributo [Authorize] 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 Razor PagesApplying policies to Razor Pages

Las directivas se aplican a Razor Pages mediante el atributo [Authorize] 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
{
}

También se pueden aplicar directivas a Razor Pages mediante una Convención de 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 una Directiva puede usar para evaluar la entidad de seguridad de 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 "AtLeast21", el requisito es un parámetro único—la edad mínima.In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. Un requisito implementa IAuthorizationRequirement, que es una interfaz de marcador vacía.A requirement implements IAuthorizationRequirement, which is an empty marker interface. Un requisito de edad mínima parametrizada podría implementarse de la siguiente manera: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 cumplirse todos los requisitos para que la evaluación de la Directiva 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, los distintos requisitos de autorización que se agregan a una sola directiva de autorización se tratan de manera y .In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

Nota

No es necesario que un requisito tenga datos ni 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 las 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 con respecto a un AuthorizationHandlerContext proporcionado para determinar si se permite el acceso.The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

Un requisito puede tener varios controladores.A requirement can have multiple handlers. Un controlador puede heredar AuthorizationHandler<TRequirement >, donde TRequirement es el requisito que se debe controlar.A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. Como alternativa, un controlador puede implementar 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 un requisitoUse a handler for one requirement

El siguiente es un ejemplo de una relación uno a uno en la que un controlador de edad mínima emplea un único requisito: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 seguridad de usuario actual tiene una declaración de nacimiento de fecha que ha sido emitida 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 demanda, 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 existe una demanda, se calcula la edad del usuario.When a claim is present, the user's age is calculated. Si el usuario cumple la edad mínima definida por el requisito, la autorización se considera correcta.If the user meets the minimum age defined by the requirement, authorization is deemed successful. Cuando la autorización se realiza correctamente, 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 de uno a varios en la que un controlador de permisos puede controlar tres tipos diferentes de 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 atraviesa PendingRequirements—una propiedad que contiene requisitos no marcados como correctos.The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. Para un requisito ReadPermission, el usuario debe ser un propietario o un patrocinador para tener acceso 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 requisito EditPermission o DeletePermission, debe ser un propietario para tener acceso 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.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

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

El código anterior registra MinimumAgeHandler como singleton mediante la invocación de services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();.The preceding code registers MinimumAgeHandler as a singleton by invoking services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Los controladores se pueden registrar con cualquiera de las duraciones de serviciointegradas.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 método Handle del ejemplo de controlador no devuelve ningún valor.Note that the Handle method in the handler example returns no value. ¿Cómo se indica el estado correcto o incorrecto?How is a status of either success or failure indicated?

  • Un controlador indica que la operación se realizó correctamente mediante una llamada a context.Succeed(IAuthorizationRequirement requirement), pasando el requisito 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 necesita controlar los errores por lo general, ya que otros controladores para el mismo requisito pueden realizarse correctamente.A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

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

Si un controlador llama a context.Succeed o context.Fail, todavía se llama a todos los demás controladores.If a handler calls context.Succeed or context.Fail, all other handlers are still called. Esto permite a los requisitos producir efectos secundarios, como el registro, que tiene lugar incluso si otro controlador ha validado o no un requisito correctamente.This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. Cuando se establece en false, la propiedad InvokeHandlersAfterFailure (disponible en ASP.net Core 1,1 y versiones posteriores) cortocircuita la ejecución de los controladores cuando se llama a context.Fail.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 tiene como valor predeterminado true, en cuyo caso se llama a todos los controladores.InvokeHandlersAfterFailure defaults to true, in which case all handlers are called.

Nota

Se llama a los controladores de autorización incluso si se produce un error de autenticación.Authorization handlers are called even if authentication fails.

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

En los casos en los que desea que la evaluación esté en una base de o , implemente varios controladores para un único requisito.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 solo se abren con tarjetas clave.For example, Microsoft has doors which only open with key cards. Si deja la tarjeta de la llave en casa, el recepcionista imprime una etiqueta 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 único requisito, BuildingEntry, pero varios controladores, cada uno examinando un único requisito.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 registrados.Ensure that both handlers are registered. Si alguno de los controladores se realiza correctamente cuando una directiva evalúa el BuildingEntryRequirement, la evaluación de la Directiva se realiza correctamente.If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

Usar un FUNC para cumplir una directivaUsing a func to fulfill a policy

Puede haber situaciones en las que el cumplimiento de una directiva sea fácil de expresar en el 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 generador de directivas de RequireAssertion.It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

Por ejemplo, el BadgeEntryHandler anterior podría volver a escribirse de la siguiente manera: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 los controladoresAccessing MVC request context in handlers

El método HandleRequirementAsync que implementa en un controlador de autorización tiene dos parámetros: un AuthorizationHandlerContext y el TRequirement que está controlando.The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. Los marcos como MVC o Jabbr pueden agregar cualquier objeto a la propiedad Resource 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 la propiedad Resource.For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. Esta propiedad proporciona acceso a HttpContext, RouteDatay todo lo demás proporcionado por MVC y Razor Pages.This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

El uso de la propiedad Resource es específico del marco de trabajo.The use of the Resource property is framework specific. El uso de la información en la propiedad Resource limita las directivas de autorización a marcos concretos.Using information in the Resource property limits your authorization policies to particular frameworks. Debe convertir la propiedad Resource mediante la palabra clave is y, a continuación, confirmar que la conversión se ha realizado correctamente para asegurarse de que el código no se bloquea 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.
}

En segundo plano, la autorización basada en roles y la autorización basada en notificaciones usan un requisito, un controlador de requisitos 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 admiten 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 más enriquecida, reutilizable y comprobable.The result is a richer, reusable, testable authorization structure.

Una directiva de autorización se compone de uno o varios requisitos.An authorization policy consists of one or more requirements. Se registra como parte de la configuración del servicio de autorización, en el método Startup.ConfigureServices: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 "AtLeast21".In the preceding example, an "AtLeast21" policy is created. Tiene un único requisito—de una edad mínima, que se proporciona como un parámetro al requisito.It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement.

IAuthorizationServiceIAuthorizationService

El servicio principal que determina si la autorización se realiza correctamente es IAuthorizationService: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);
}

En el código anterior se resaltan los dos métodos de IAuthorizationService.The preceding code highlights the two methods of the IAuthorizationService.

IAuthorizationRequirement es un servicio de marcador sin métodos y el mecanismo para hacer un seguimiento de si la autorización se realiza correctamente.IAuthorizationRequirement is a marker service with no methods, and the mechanism for tracking whether authorization is successful.

Cada IAuthorizationHandler es responsable de comprobar si se cumplen los requisitos: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 clase AuthorizationHandlerContext es lo que el controlador utiliza para indicar si se han cumplido los requisitos:The AuthorizationHandlerContext class is what the handler uses to mark whether requirements have been met:

 context.Succeed(requirement)

En el código siguiente se muestra la implementación predeterminada simplificada (y anotada con comentarios) del servicio de autorización: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);
}

En el código siguiente se muestra un ConfigureServicestípico: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);
}

Use IAuthorizationService o [Authorize(Policy = "Something")] para la autorización.Use IAuthorizationService or [Authorize(Policy = "Something")] for authorization.

Aplicar directivas a los controladores de MVCApplying policies to MVC controllers

Si utiliza Razor Pages, consulte aplicar directivas a Razor pages 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 atributo [Authorize] 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 Razor PagesApplying policies to Razor Pages

Las directivas se aplican a Razor Pages mediante el atributo [Authorize] 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
{
}

También se pueden aplicar directivas a Razor Pages mediante una Convención de 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 una Directiva puede usar para evaluar la entidad de seguridad de 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 "AtLeast21", el requisito es un parámetro único—la edad mínima.In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. Un requisito implementa IAuthorizationRequirement, que es una interfaz de marcador vacía.A requirement implements IAuthorizationRequirement, which is an empty marker interface. Un requisito de edad mínima parametrizada podría implementarse de la siguiente manera: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 cumplirse todos los requisitos para que la evaluación de la Directiva 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, los distintos requisitos de autorización que se agregan a una sola directiva de autorización se tratan de manera y .In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

Nota

No es necesario que un requisito tenga datos ni 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 las 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 con respecto a un AuthorizationHandlerContext proporcionado para determinar si se permite el acceso.The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

Un requisito puede tener varios controladores.A requirement can have multiple handlers. Un controlador puede heredar AuthorizationHandler<TRequirement >, donde TRequirement es el requisito que se debe controlar.A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. Como alternativa, un controlador puede implementar 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 un requisitoUse a handler for one requirement

El siguiente es un ejemplo de una relación uno a uno en la que un controlador de edad mínima emplea un único requisito: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 seguridad de usuario actual tiene una declaración de nacimiento de fecha que ha sido emitida 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 demanda, 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 existe una demanda, se calcula la edad del usuario.When a claim is present, the user's age is calculated. Si el usuario cumple la edad mínima definida por el requisito, la autorización se considera correcta.If the user meets the minimum age defined by the requirement, authorization is deemed successful. Cuando la autorización se realiza correctamente, 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 de uno a varios en la que un controlador de permisos puede controlar tres tipos diferentes de 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 atraviesa PendingRequirements—una propiedad que contiene requisitos no marcados como correctos.The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. Para un requisito ReadPermission, el usuario debe ser un propietario o un patrocinador para tener acceso 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 requisito EditPermission o DeletePermission, debe ser un propietario para tener acceso 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 singleton mediante la invocación de services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();.The preceding code registers MinimumAgeHandler as a singleton by invoking services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Los controladores se pueden registrar con cualquiera de las duraciones de serviciointegradas.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 método Handle del ejemplo de controlador no devuelve ningún valor.Note that the Handle method in the handler example returns no value. ¿Cómo se indica el estado correcto o incorrecto?How is a status of either success or failure indicated?

  • Un controlador indica que la operación se realizó correctamente mediante una llamada a context.Succeed(IAuthorizationRequirement requirement), pasando el requisito 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 necesita controlar los errores por lo general, ya que otros controladores para el mismo requisito pueden realizarse correctamente.A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

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

Si un controlador llama a context.Succeed o context.Fail, todavía se llama a todos los demás controladores.If a handler calls context.Succeed or context.Fail, all other handlers are still called. Esto permite a los requisitos producir efectos secundarios, como el registro, que tiene lugar incluso si otro controlador ha validado o no un requisito correctamente.This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. Cuando se establece en false, la propiedad InvokeHandlersAfterFailure (disponible en ASP.net Core 1,1 y versiones posteriores) cortocircuita la ejecución de los controladores cuando se llama a context.Fail.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 tiene como valor predeterminado true, en cuyo caso se llama a todos los controladores.InvokeHandlersAfterFailure defaults to true, in which case all handlers are called.

Nota

Se llama a los controladores de autorización incluso si se produce un error de autenticación.Authorization handlers are called even if authentication fails.

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

En los casos en los que desea que la evaluación esté en una base de o , implemente varios controladores para un único requisito.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 solo se abren con tarjetas clave.For example, Microsoft has doors which only open with key cards. Si deja la tarjeta de la llave en casa, el recepcionista imprime una etiqueta 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 único requisito, BuildingEntry, pero varios controladores, cada uno examinando un único requisito.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 registrados.Ensure that both handlers are registered. Si alguno de los controladores se realiza correctamente cuando una directiva evalúa el BuildingEntryRequirement, la evaluación de la Directiva se realiza correctamente.If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

Usar un FUNC para cumplir una directivaUsing a func to fulfill a policy

Puede haber situaciones en las que el cumplimiento de una directiva sea fácil de expresar en el 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 generador de directivas de RequireAssertion.It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

Por ejemplo, el BadgeEntryHandler anterior podría volver a escribirse de la siguiente manera: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 los controladoresAccessing MVC request context in handlers

El método HandleRequirementAsync que implementa en un controlador de autorización tiene dos parámetros: un AuthorizationHandlerContext y el TRequirement que está controlando.The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. Los marcos como MVC o Jabbr pueden agregar cualquier objeto a la propiedad Resource 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 la propiedad Resource.For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. Esta propiedad proporciona acceso a HttpContext, RouteDatay todo lo demás proporcionado por MVC y Razor Pages.This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

El uso de la propiedad Resource es específico del marco de trabajo.The use of the Resource property is framework specific. El uso de la información en la propiedad Resource limita las directivas de autorización a marcos concretos.Using information in the Resource property limits your authorization policies to particular frameworks. Debe convertir la propiedad Resource mediante la palabra clave is y, a continuación, confirmar que la conversión se ha realizado correctamente para asegurarse de que el código no se bloquea 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.
}