Autorização baseada em políticas no ASP.NET Core

Sob os bastidores, a autorização baseada em função e a autorização baseada em declarações usam um requisito, um manipulador de requisitos e uma política pré-configurada. Esses blocos de construção dão suporte à expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais rica, reutilizável e que pode ser testada.

Uma política de autorização consiste em um ou mais requisitos. Registre-o como parte da configuração do serviço de autorização no arquivo Program. cs do aplicativo:

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

No exemplo anterior, uma política "AtLeast21" é criada. Ele tem um requisito único — de uma idade mínima, que é fornecida como um parâmetro para o requisito.

IAuthorizationService

O serviço primário que determina se a autorização é bem-sucedida é 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);
}

O código anterior realça os dois métodos do IAuthorizationService.

IAuthorizationRequirement é um serviço de marcador sem métodos e o mecanismo para controlar se a autorização é bem-sucedida.

Cada uma IAuthorizationHandler é responsável por verificar se os requisitos são atendidos:

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

A AuthorizationHandlerContext classe é o que o manipulador usa para marcar se os requisitos foram atendidos:

 context.Succeed(requirement)

O código a seguir mostra a implementação padrão simplificada (e anotada com comentários) do serviço de autorização:

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

O código a seguir mostra uma configuração típica do serviço de autorização:

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

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

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

Use IAuthorizationService , [Authorize(Policy = "Something")] ou RequireAuthorization("Something") para autorização.

Aplicar políticas a controladores MVC

Para aplicativos que usam Razor páginas, consulte a seção aplicar políticas a Razor páginas .

Aplique políticas a controladores usando o [Authorize] atributo com o nome da política. Por exemplo:

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

namespace AuthorizationPoliciesSample.Controllers;

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

Aplicar políticas a Razor páginas

Aplique políticas a Razor páginas usando o [Authorize] atributo com o nome da política. Por exemplo:

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

namespace AuthorizationPoliciesSample.Pages;

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

As políticas não podem ser aplicadas no Razor nível do manipulador de páginas, elas devem ser aplicadas à página.

As políticas também podem ser aplicadas a Razor páginas usando uma Convenção de autorização.

Aplicar políticas a pontos de extremidade

Aplique políticas a pontos de extremidade usando RequireAuthorization com o nome da política. Por exemplo:

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

Requisitos

Um requisito de autorização é uma coleção de parâmetros de dados que uma política pode usar para avaliar a entidade de usuário atual. Em nossa política de "AtLeast21", o requisito é um único parâmetro — de idade mínima. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia. Um requisito de idade mínima parametrizada poderia ser implementado da seguinte maneira:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

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

    public int MinimumAge { get; }
}

Se uma política de autorização contiver vários requisitos de autorização, todos os requisitos deverão ser aprovados para que a avaliação da política seja realizada com êxito. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados em uma base e .

Observação

Um requisito não precisa ter dados ou propriedades.

Manipuladores de autorização

Um manipulador de autorização é responsável pela avaliação das propriedades de um requisito. O manipulador de autorização avalia os requisitos em relação a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar <TRequirement> AuthorizationHandler, em que TRequirement é o requisito a ser manipulado. Como alternativa, um manipulador pode implementar IAuthorizationHandler diretamente para lidar com mais de um tipo de requisito.

Usar um manipulador para um requisito

O exemplo a seguir mostra uma relação um-para-um na qual um manipulador de idade mínima lida com um único requisito:

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

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

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

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

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

        return Task.CompletedTask;
    }
}

O código anterior determina se a entidade de usuário atual tem uma declaração de data de nascimento que foi emitida por um emissor conhecido e confiável. A autorização não pode ocorrer quando a declaração está ausente, caso em que uma tarefa concluída é retornada. Quando uma declaração está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização será considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocada com o requisito atendido como seu único parâmetro.

Usar um manipulador para vários requisitos

O exemplo a seguir mostra uma relação um-para-muitos na qual um manipulador de permissão pode lidar com três tipos diferentes de requisitos:

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

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}

O código anterior percorre PendingRequirements — uma propriedade que contém os requisitos não marcados como bem-sucedidos. Para um ReadPermission requisito, o usuário deve ser um proprietário ou um patrocinador para acessar o recurso solicitado. No caso de um EditPermission requisito ou DeletePermission , eles devem ser um proprietário para acessar o recurso solicitado.

Registro de manipulador

Registre manipuladores na coleção de serviços durante a configuração. Por exemplo:

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

O código anterior é registrado MinimumAgeHandler como um singleton. Os manipuladores podem ser registrados usando qualquer tempo de vida de serviçointerno.

É possível agrupar um requisito e um manipulador em uma única classe implementando IAuthorizationRequirement e IAuthorizationHandler . Isso cria um acoplamento rígido entre o manipulador e o requisito e é recomendado apenas para requisitos e manipuladores simples. A criação de uma classe que implementa ambas as interfaces elimina a necessidade de registrar o manipulador em DI devido ao PassThroughAuthorizationHandler interno que permite que os requisitos se manipulem.

Consulte a classe AssertionRequirement para obter um bom exemplo em que o AssertionRequirement é um requisito e o manipulador em uma classe totalmente independente.

O que um manipulador deve retornar?

Observe que o Handle método no exemplo do manipulador não retorna nenhum valor. Como é indicado um status de êxito ou falha?

  • Um manipulador indica êxito chamando context.Succeed(IAuthorizationRequirement requirement) , passando o requisito que foi validado com êxito.

  • Um manipulador não precisa lidar com falhas geralmente, já que outros manipuladores para o mesmo requisito podem ser bem-sucedidos.

  • Para garantir a falha, mesmo que outros manipuladores de requisitos tenham êxito, chame context.Fail .

Se um manipulador chamar context.Succeed ou context.Fail , todos os outros manipuladores ainda serão chamados. Isso permite que os requisitos produzam efeitos colaterais, como registro em log, que ocorre mesmo se outro manipulador tiver validado ou reprovado com êxito um requisito. Quando definido como false , a propriedade InvokeHandlersAfterFailure de circuitos curtos é a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure``trueo padrão é, nesse caso, todos os manipuladores são chamados.

Observação

Os manipuladores de autorização são chamados mesmo se a autenticação falhar.

Por que desejo vários manipuladores para um requisito?

Nos casos em que você deseja que a avaliação seja em um ou base, implemente vários manipuladores para um único requisito. Por exemplo, a Microsoft tem portas que só são abertas com cartões-chave. Se você deixar o cartão-chave em casa, o recepcionista imprime um adesivo temporário e abre a porta para você. Nesse cenário, você teria um único requisito, BuildingEntry, mas vários manipuladores, cada um examinando um único requisito.

BuildingEntryRequirement. cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler. cs

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

namespace AuthorizationPoliciesSample.Policies.Handlers;

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

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler. cs

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

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Verifique se ambos os manipuladores estão registrados. Se um dos manipuladores for executado com sucesso quando uma política avaliar o BuildingEntryRequirement , a avaliação da política será realizada com sucesso.

Usar um Func para atender a uma política

Pode haver situações em que o cumprimento de uma política é simples de expressar no código. É possível fornecer um Func<AuthorizationHandlerContext, bool> ao configurar uma política com o construtor de RequireAssertion política.

Por exemplo, o anterior BadgeEntryHandler poderia ser reescrito da seguinte maneira:

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

Acessar o contexto de solicitação MVC nos manipuladores

O HandleRequirementAsync método tem dois parâmetros: AuthorizationHandlerContext a e o TRequirement que está sendo manipulado. Estruturas como o MVC ou SignalR são livres para adicionar qualquer objeto à Resource propriedade no AuthorizationHandlerContext para passar informações extras.

Ao usar o roteamento de ponto de extremidade, a autorização normalmente é manipulada pelo middleware de autorização. Nesse caso, a Resource propriedade é uma instância do HttpContext . O contexto pode ser usado para acessar o ponto de extremidade atual, que pode ser usado para investigar o recurso subjacente ao qual você está encaminhando. Por exemplo:

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

Com o roteamento tradicional, ou quando a autorização acontece como parte do filtro de autorização do MVC, o valor de Resource é uma AuthorizationFilterContext instância. Essa propriedade fornece acesso a HttpContext , RouteData e a todos os outros fornecidos pelo MVC e por Razor páginas.

O uso da Resource propriedade é específico da estrutura. O uso de informações na Resource Propriedade limita suas políticas de autorização a estruturas específicas. Você deve converter a Resource propriedade usando a is palavra-chave e, em seguida, confirmar se a conversão foi bem-sucedida para garantir que seu código não falhe com um InvalidCastException quando executado em outras estruturas:

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

Exigir globalmente que todos os usuários sejam autenticados

Para obter informações sobre como exigir globalmente que todos os usuários sejam autenticados, consulte exigir usuários autenticados.

Sob os bastidores, a autorização baseada em função e a autorização baseada em declarações usam um requisito, um manipulador de requisitos e uma política pré-configurada. Esses blocos de construção dão suporte à expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais rica, reutilizável e que pode ser testada.

Uma política de autorização consiste em um ou mais requisitos. Ele é registrado como parte da configuração do serviço de autorização, no Startup.ConfigureServices método:

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

No exemplo anterior, uma política "AtLeast21" é criada. Ele tem um requisito único — de uma idade mínima, que é fornecida como um parâmetro para o requisito.

IAuthorizationService

O serviço primário que determina se a autorização é bem-sucedida é 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);
}

O código anterior realça os dois métodos do IAuthorizationService.

IAuthorizationRequirement é um serviço de marcador sem métodos e o mecanismo para controlar se a autorização é bem-sucedida.

Cada uma IAuthorizationHandler é responsável por verificar se os requisitos são atendidos:

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

A AuthorizationHandlerContext classe é o que o manipulador usa para marcar se os requisitos foram atendidos:

 context.Succeed(requirement)

O código a seguir mostra a implementação padrão simplificada (e anotada com comentários) do serviço de autorização:

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

O código a seguir mostra um típico 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 ou [Authorize(Policy = "Something")] para autorização.

Aplicar políticas a controladores MVC

Se você estiver usando Razor páginas, consulte aplicar políticas a Razor páginas neste documento.

As políticas são aplicadas aos controladores usando o atributo [Authorize] com o nome da política. Por exemplo:

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

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

Aplicar políticas a Razor páginas

As políticas são aplicadas às Razor páginas usando o [Authorize] atributo com o nome da política. Por exemplo:

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

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

As políticas não podem ser aplicadas no Razor nível do manipulador de páginas, elas devem ser aplicadas à página.

As políticas podem ser aplicadas a Razor páginas usando uma Convenção de autorização.

Requisitos

Um requisito de autorização é uma coleção de parâmetros de dados que uma política pode usar para avaliar a entidade de usuário atual. Em nossa política de "AtLeast21", o requisito é um único parâmetro — de idade mínima. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia. Um requisito de idade mínima parametrizada poderia ser implementado da seguinte maneira:

using Microsoft.AspNetCore.Authorization;

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

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

Se uma política de autorização contiver vários requisitos de autorização, todos os requisitos deverão ser aprovados para que a avaliação da política seja realizada com êxito. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados em uma base e .

Observação

Um requisito não precisa ter dados ou propriedades.

Manipuladores de autorização

Um manipulador de autorização é responsável pela avaliação das propriedades de um requisito. O manipulador de autorização avalia os requisitos em relação a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar <TRequirement> AuthorizationHandler, em que TRequirement é o requisito a ser manipulado. Como alternativa, um manipulador pode implementar IAuthorizationHandler para lidar com mais de um tipo de requisito.

Usar um manipulador para um requisito

Veja a seguir um exemplo de uma relação um-para-um na qual um manipulador de idade mínima utiliza um único requisito:

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

O código anterior determina se a entidade de usuário atual tem uma declaração de data de nascimento que foi emitida por um emissor conhecido e confiável. A autorização não pode ocorrer quando a declaração está ausente, caso em que uma tarefa concluída é retornada. Quando uma declaração está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização será considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocada com o requisito atendido como seu único parâmetro.

Usar um manipulador para vários requisitos

Veja a seguir um exemplo de uma relação um-para-muitos na qual um manipulador de permissão pode lidar com três tipos diferentes de requisitos:

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

O código anterior percorre PendingRequirements — uma propriedade que contém os requisitos não marcados como bem-sucedidos. Para um ReadPermission requisito, o usuário deve ser um proprietário ou um patrocinador para acessar o recurso solicitado. No caso de um EditPermission requisito ou DeletePermission , ele deve ser um proprietário para acessar o recurso solicitado.

Registro de manipulador

Os manipuladores são registrados na coleção de serviços durante a configuração. Por exemplo:

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

O código anterior é registrado MinimumAgeHandler como um singleton invocando services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); . Os manipuladores podem ser registrados usando qualquer tempo de vida de serviçointerno.

É possível agrupar um requisito e um manipulador em uma única classe implementando IAuthorizationRequirement e IAuthorizationHandler . Isso cria um acoplamento rígido entre o manipulador e o requisito e é recomendado apenas para requisitos e manipuladores simples. A criação de uma classe que implementa ambas as interfaces elimina a necessidade de registrar o manipulador em DI devido ao PassThroughAuthorizationHandler interno que permite que os requisitos se manipulem.

Consulte a classe AssertionRequirement para obter um bom exemplo em que o AssertionRequirement é um requisito e o manipulador em uma classe totalmente independente.

O que um manipulador deve retornar?

Observe que o Handle método no exemplo do manipulador não retorna nenhum valor. Como é indicado um status de êxito ou falha?

  • Um manipulador indica êxito chamando context.Succeed(IAuthorizationRequirement requirement) , passando o requisito que foi validado com êxito.

  • Um manipulador não precisa lidar com falhas geralmente, já que outros manipuladores para o mesmo requisito podem ser bem-sucedidos.

  • Para garantir a falha, mesmo que outros manipuladores de requisitos tenham êxito, chame context.Fail .

Se um manipulador chamar context.Succeed ou context.Fail , todos os outros manipuladores ainda serão chamados. Isso permite que os requisitos produzam efeitos colaterais, como registro em log, que ocorre mesmo se outro manipulador tiver validado ou reprovado com êxito um requisito. Quando definido como false , a propriedade InvokeHandlersAfterFailure de circuitos curtos é a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure``trueo padrão é, nesse caso, todos os manipuladores são chamados.

Observação

Os manipuladores de autorização são chamados mesmo se a autenticação falhar.

Por que desejo vários manipuladores para um requisito?

Nos casos em que você deseja que a avaliação seja em um ou base, implemente vários manipuladores para um único requisito. Por exemplo, a Microsoft tem portas que só são abertas com cartões-chave. Se você deixar o cartão-chave em casa, o recepcionista imprime um adesivo temporário e abre a porta para você. Nesse cenário, você teria um único requisito, BuildingEntry, mas vários manipuladores, cada um examinando um único requisito.

BuildingEntryRequirement. cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler. cs

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

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

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

TemporaryStickerHandler. cs

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

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

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

Verifique se ambos os manipuladores estão registrados. Se um dos manipuladores for executado com sucesso quando uma política avaliar o BuildingEntryRequirement , a avaliação da política será realizada com sucesso.

Usar um Func para atender a uma política

Pode haver situações em que o cumprimento de uma política é simples de expressar no código. É possível fornecer um Func<AuthorizationHandlerContext, bool> ao configurar sua política com o RequireAssertion Policy Builder.

Por exemplo, o anterior BadgeEntryHandler poderia ser reescrito da seguinte maneira:

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

Acessar o contexto de solicitação MVC nos manipuladores

O HandleRequirementAsync método que você implementa em um manipulador de autorização tem dois parâmetros: um AuthorizationHandlerContext e o TRequirement que você está lidando. Estruturas como o MVC ou SignalR são livres para adicionar qualquer objeto à Resource propriedade no AuthorizationHandlerContext para passar informações extras.

Ao usar o roteamento de ponto de extremidade, a autorização normalmente é manipulada pelo middleware de autorização. Nesse caso, a Resource propriedade é uma instância do HttpContext . O contexto pode ser usado para acessar o ponto de extremidade atual, que pode ser usado para investigar o recurso subjacente ao qual você está encaminhando. Por exemplo:

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

Com o roteamento tradicional, ou quando a autorização acontece como parte do filtro de autorização do MVC, o valor de Resource é uma AuthorizationFilterContext instância. Essa propriedade fornece acesso a HttpContext , RouteData e a todos os outros fornecidos pelo MVC e por Razor páginas.

O uso da Resource propriedade é específico da estrutura. O uso de informações na Resource Propriedade limita suas políticas de autorização a estruturas específicas. Você deve converter a Resource propriedade usando a is palavra-chave e, em seguida, confirmar se a conversão foi bem-sucedida para garantir que seu código não falhe com um InvalidCastException quando executado em outras estruturas:

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

Exigir globalmente que todos os usuários sejam autenticados

Para obter informações sobre como exigir globalmente que todos os usuários sejam autenticados, consulte exigir usuários autenticados.

Sob os bastidores, a autorização baseada em função e a autorização baseada em declarações usam um requisito, um manipulador de requisitos e uma política pré-configurada. Esses blocos de construção dão suporte à expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais rica, reutilizável e que pode ser testada.

Uma política de autorização consiste em um ou mais requisitos. Ele é registrado como parte da configuração do serviço de autorização, no Startup.ConfigureServices método:

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

No exemplo anterior, uma política "AtLeast21" é criada. Ele tem um requisito único — de uma idade mínima, que é fornecida como um parâmetro para o requisito.

IAuthorizationService

O serviço primário que determina se a autorização é bem-sucedida é 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);
}

O código anterior realça os dois métodos do IAuthorizationService.

IAuthorizationRequirement é um serviço de marcador sem métodos e o mecanismo para controlar se a autorização é bem-sucedida.

Cada uma IAuthorizationHandler é responsável por verificar se os requisitos são atendidos:

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

A AuthorizationHandlerContext classe é o que o manipulador usa para marcar se os requisitos foram atendidos:

 context.Succeed(requirement)

O código a seguir mostra a implementação padrão simplificada (e anotada com comentários) do serviço de autorização:

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

O código a seguir mostra um típico 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 ou [Authorize(Policy = "Something")] para autorização.

Aplicar políticas a controladores MVC

Se você estiver usando Razor páginas, consulte aplicar políticas a Razor páginas neste documento.

As políticas são aplicadas aos controladores usando o atributo [Authorize] com o nome da política. Por exemplo:

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

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

Aplicar políticas a Razor páginas

As políticas são aplicadas às Razor páginas usando o [Authorize] atributo com o nome da política. Por exemplo:

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

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

As políticas não podem ser aplicadas no Razor nível do manipulador de páginas, elas devem ser aplicadas à página.

As políticas podem ser aplicadas a Razor páginas usando uma Convenção de autorização.

Requisitos

Um requisito de autorização é uma coleção de parâmetros de dados que uma política pode usar para avaliar a entidade de usuário atual. Em nossa política de "AtLeast21", o requisito é um único parâmetro — de idade mínima. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia. Um requisito de idade mínima parametrizada poderia ser implementado da seguinte maneira:

using Microsoft.AspNetCore.Authorization;

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

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

Se uma política de autorização contiver vários requisitos de autorização, todos os requisitos deverão ser aprovados para que a avaliação da política seja realizada com êxito. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados em uma base e .

Observação

Um requisito não precisa ter dados ou propriedades.

Manipuladores de autorização

Um manipulador de autorização é responsável pela avaliação das propriedades de um requisito. O manipulador de autorização avalia os requisitos em relação a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar <TRequirement> AuthorizationHandler, em que TRequirement é o requisito a ser manipulado. Como alternativa, um manipulador pode implementar IAuthorizationHandler para lidar com mais de um tipo de requisito.

Usar um manipulador para um requisito

Veja a seguir um exemplo de uma relação um-para-um na qual um manipulador de idade mínima utiliza um único requisito:

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

O código anterior determina se a entidade de usuário atual tem uma declaração de data de nascimento que foi emitida por um emissor conhecido e confiável. A autorização não pode ocorrer quando a declaração está ausente, caso em que uma tarefa concluída é retornada. Quando uma declaração está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização será considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocada com o requisito atendido como seu único parâmetro.

Usar um manipulador para vários requisitos

Veja a seguir um exemplo de uma relação um-para-muitos na qual um manipulador de permissão pode lidar com três tipos diferentes de requisitos:

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

O código anterior percorre PendingRequirements — uma propriedade que contém os requisitos não marcados como bem-sucedidos. Para um ReadPermission requisito, o usuário deve ser um proprietário ou um patrocinador para acessar o recurso solicitado. No caso de um EditPermission requisito ou DeletePermission , ele deve ser um proprietário para acessar o recurso solicitado.

Registro de manipulador

Os manipuladores são registrados na coleção de serviços durante a configuração. Por exemplo:

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

O código anterior é registrado MinimumAgeHandler como um singleton invocando services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); . Os manipuladores podem ser registrados usando qualquer tempo de vida de serviçointerno.

O que um manipulador deve retornar?

Observe que o Handle método no exemplo do manipulador não retorna nenhum valor. Como é indicado um status de êxito ou falha?

  • Um manipulador indica êxito chamando context.Succeed(IAuthorizationRequirement requirement) , passando o requisito que foi validado com êxito.

  • Um manipulador não precisa lidar com falhas geralmente, já que outros manipuladores para o mesmo requisito podem ser bem-sucedidos.

  • Para garantir a falha, mesmo que outros manipuladores de requisitos tenham êxito, chame context.Fail .

Se um manipulador chamar context.Succeed ou context.Fail , todos os outros manipuladores ainda serão chamados. Isso permite que os requisitos produzam efeitos colaterais, como registro em log, que ocorre mesmo se outro manipulador tiver validado ou reprovado com êxito um requisito. Quando definido como false , a propriedade InvokeHandlersAfterFailure de circuitos curtos é a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure``trueo padrão é, nesse caso, todos os manipuladores são chamados.

Observação

Os manipuladores de autorização são chamados mesmo se a autenticação falhar.

Por que desejo vários manipuladores para um requisito?

Nos casos em que você deseja que a avaliação seja em um ou base, implemente vários manipuladores para um único requisito. Por exemplo, a Microsoft tem portas que só são abertas com cartões-chave. Se você deixar o cartão-chave em casa, o recepcionista imprime um adesivo temporário e abre a porta para você. Nesse cenário, você teria um único requisito, BuildingEntry, mas vários manipuladores, cada um examinando um único requisito.

BuildingEntryRequirement. cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler. cs

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

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

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

TemporaryStickerHandler. cs

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

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

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

Verifique se ambos os manipuladores estão registrados. Se um dos manipuladores for executado com sucesso quando uma política avaliar o BuildingEntryRequirement , a avaliação da política será realizada com sucesso.

Usar um Func para atender a uma política

Pode haver situações em que o cumprimento de uma política é simples de expressar no código. É possível fornecer um Func<AuthorizationHandlerContext, bool> ao configurar sua política com o RequireAssertion Policy Builder.

Por exemplo, o anterior BadgeEntryHandler poderia ser reescrito da seguinte maneira:

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

Acessar o contexto de solicitação MVC nos manipuladores

O HandleRequirementAsync método que você implementa em um manipulador de autorização tem dois parâmetros: um AuthorizationHandlerContext e o TRequirement que você está lidando. Estruturas como o MVC ou SignalR são livres para adicionar qualquer objeto à Resource propriedade no AuthorizationHandlerContext para passar informações extras.

Ao usar o roteamento de ponto de extremidade, a autorização normalmente é manipulada pelo middleware de autorização. Nesse caso, a Resource propriedade é uma instância do Endpoint . O ponto de extremidade pode ser usado para investigar o recurso subjacente ao qual você está encaminhando. Por exemplo:

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

O ponto de extremidade não fornece acesso ao atual HttpContext . Ao usar o roteamento de ponto de extremidade, use IHttpContextAcessor para acessar HttpContext dentro de um manipulador de autorização. Para obter mais informações, consulte usar HttpContext de componentes personalizados.

Com o roteamento tradicional, ou quando a autorização acontece como parte do filtro de autorização do MVC, o valor de Resource é uma AuthorizationFilterContext instância. Essa propriedade fornece acesso a HttpContext , RouteData e a todos os outros fornecidos pelo MVC e por Razor páginas.

O uso da Resource propriedade é específico da estrutura. O uso de informações na Resource Propriedade limita suas políticas de autorização a estruturas específicas. Você deve converter a Resource propriedade usando a is palavra-chave e, em seguida, confirmar se a conversão foi bem-sucedida para garantir que seu código não falhe com um InvalidCastException quando executado em outras estruturas:

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

Exigir globalmente que todos os usuários sejam autenticados

Para obter informações sobre como exigir globalmente que todos os usuários sejam autenticados, consulte exigir usuários autenticados.