Partilhar via


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

Abaixo das capas, 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 suportam a expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais rica, reutilizável e testável.

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 do Program.cs aplicativo:

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

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

IAuthorizationService

O serviço principal que determina se a autorização foi 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 destaca os dois métodos do IAuthorizationService.

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

Cada um IAuthorizationHandler é responsável por verificar se os requisitos são cumpridos:

/// <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<IAuthorizationHandler> 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 aplicações que utilizam Razor Páginas, consulte a secção Aplicar políticas a Razor Páginas .

Aplique políticas aos controladores usando o [Authorize] atributo com o nome da política:

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

Se várias políticas forem aplicadas nos níveis de controlador e ação, todas as políticas deverão passar antes que o acesso seja concedido:

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

Aplicar políticas às Razor Páginas

Aplique políticas ao Razor Pages 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 nível do manipulador de Razor Página, elas devem ser aplicadas à Página.

As políticas também podem ser aplicadas às 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");

Requirements

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

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 bem-sucedida. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados com base em E .

Note

Um requisito não precisa ter dados ou propriedades.

Gestores 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 relativamente a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar AuthorizationHandler<TRequirement>, onde 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 gestor de idade mínima gere uma única exigência.

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 o principal do usuário atual tem uma reivindicação de data de nascimento 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 reivindicação está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização é considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocado com o requisito satisfeito 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ões 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 atravessa uma propriedade PendingRequirements, que contém 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 requisito EditPermission ou DeletePermission, devem ser proprietários para aceder ao recurso solicitado.

Registo do handler

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

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

O código anterior regista-se MinimumAgeHandler como um singleton. Os manipuladores podem ser registrados usando qualquer um dos tempos de vida de serviço internos.

É possível agrupar um requisito e um manipulador em uma única classe que implemente ambos IAuthorizationRequirement e IAuthorizationHandler. Esse agrupamento cria um acoplamento estreito entre o manipulador e o requisito e só é recomendado para requisitos e manipuladores simples. Criar uma classe que implementa ambas as interfaces elimina a necessidade de registrar o manipulador em DI por causa do built-in PassThroughAuthorizationHandler que permite que os requisitos manipulem a si mesmos.

Veja a AssertionRequirement implementação da classe para um bom exemplo onde 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 sucesso ou fracasso?

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

  • Um manipulador não precisa lidar com falhas em geral, pois outros manipuladores para o mesmo requisito podem ter êxito.

  • Para garantir falhas, mesmo que outros manipuladores de requisitos sejam bem-sucedidos, 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 o registro, que ocorre mesmo que outro manipulador tenha validado com êxito ou falhado em um requisito. Quando definida como false, a propriedade InvokeHandlersAfterFailure curto-circuita a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure define-se como padrão true, caso em que todos os manipuladores são chamados.

Note

Os manipuladores de autorização são chamados mesmo quando a autenticação falha. Além disso, os manipuladores podem executar em qualquer ordem, portanto, não dependam de serem chamados em qualquer ordem específica.

Por que gostaria de ter vários gestores para um requisito?

Nos casos em que você deseja que a avaliação seja baseada em OR, implemente vários manipuladores para um único requisito. Por exemplo, a Microsoft tem portas que só abrem com cartões de chave. Se você deixar seu cartão-chave em casa, a rececionista 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;
    }
}

Certifique-se de que ambos os manipuladores estejam registrados. Se algum dos manipuladores for bem-sucedido quando uma política avalia o BuildingEntryRequirement, a avaliação da política é bem-sucedida.

Use uma função para implementar uma política

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

Por exemplo, o anterior BadgeEntryHandler pode ser reescrito da seguinte forma:

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 em manipuladores

O HandleRequirementAsync método tem dois parâmetros: um AuthorizationHandlerContext e o TRequirement a manipular. Frameworks como 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 é geralmente tratada pelo Middleware de Autorização. Neste caso, a Resource propriedade é uma instância de HttpContext. O contexto pode ser usado para acessar o ponto de extremidade atual, que pode ser usado para investigar o recurso subjacente para o qual você está roteando. 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. Esta propriedade fornece acesso a HttpContext, RouteData, e a tudo o mais fornecido pelo MVC e pelas Páginas Razor.

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

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

Globalmente exigem 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.

Exemplo de autorização com serviço externo

O código de exemplo em AspNetCore.Docs.Samples mostra como implementar requisitos de autorização adicionais com um serviço de autorização externo. O projeto de exemplo Contoso.API é protegido com o Azure AD. Uma verificação de autorização adicional do Contoso.Security.API projeto retorna uma carga descrevendo se o Contoso.API aplicativo cliente pode invocar a GetWeather API.

Configurar o exemplo

  • Crie um registro de aplicativo em seu locatário do Microsoft Entra ID:

  • Atribua-lhe um AppRole.

  • Em Permissões de API, adicione o AppRole como uma permissão e conceda o consentimento do administrador. Observe que, nessa configuração, esse registro de aplicativo representa a API e o cliente que invoca a API. Se quiser, pode criar dois registos de aplicações. Se você estiver usando essa configuração, certifique-se de executar apenas as permissões da API, adicione AppRole como uma etapa de permissão apenas para o cliente. Somente o registro do aplicativo cliente requer que um segredo do cliente seja gerado.

  • Configure o Contoso.API projeto com as seguintes configurações:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Tenant name from AAD properties>.onmicrosoft.com",
    "TenantId": "<Tenant Id from AAD properties>",
    "ClientId": "<Client Id from App Registration representing the API>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  • Configure Contoso.Security.API com as seguintes configurações:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  • Abra o arquivo ContosoAPI.collection.json e configure um ambiente com o seguinte:

    • ClientId: ID do cliente do registro do aplicativo que representa o cliente que chama a API.
    • clientSecret: Segredo do cliente do registo da aplicação que representa o cliente que faz chamadas à API.
    • TenantId: ID do locatário das propriedades do AAD
  • Extraia os comandos do ContosoAPI.collection.json arquivo e use-os para construir comandos cURL para testar o aplicativo.

  • Execute a solução e use cURL para invocar a API. Você pode adicionar pontos de interrupção no Contoso.Security.API.SecurityPolicyController e observar que o ID do cliente está a ser passado, o que é usado para verificar se é permitido Obter Tempo.

Recursos adicionais

Abaixo das capas, 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 suportam a expressão de avaliações de autorização no código. O resultado é uma estrutura de autorização mais rica, reutilizável e testável.

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. Tem um único requisito: o de uma idade mínima, que é fornecido como parâmetro para o requisito.

IAuthorizationService

O serviço principal que determina se a autorização foi 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 destaca os dois métodos do IAuthorizationService.

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

Cada um IAuthorizationHandler é responsável por verificar se os requisitos são cumpridos:

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

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

Aplicar políticas ao controlador MVC

Se estiver a utilizar Razor Pages, consulte Aplicar políticas em Razor Pages neste documento.

As políticas são aplicadas aos controladores usando o [Authorize] atributo 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 às Razor Páginas

As políticas são aplicadas ao Razor Pages 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 nível do manipulador de Razor Página, elas devem ser aplicadas à Página.

As políticas podem ser aplicadas às Razor Páginas usando uma convenção de autorização.

Requirements

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

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 bem-sucedida. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados com base em E .

Note

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 relativamente a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar AuthorizationHandler<TRequirement>, onde 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

O exemplo a seguir mostra uma relação um-para-um na qual um gestor de idade mínima utiliza uma única condição:

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 o principal do usuário atual tem uma reivindicação de data de nascimento 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 reivindicação está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização é considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocado com o requisito satisfeito 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ões 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 atravessa uma propriedade PendingRequirements, que contém 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. Para um EditPermission ou DeletePermission requisito, o usuário deve ser um proprietário para acessar o recurso solicitado.

Registo do manipulador

Os manipuladores são registados no conjunto 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 regista MinimumAgeHandler como singleton ao invocar services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Os manipuladores podem ser registrados usando qualquer um dos tempos de vida de serviço internos.

É possível agrupar um requisito e um manipulador em uma única classe que implemente tanto IAuthorizationRequirement quanto IAuthorizationHandler. Esse agrupamento cria um acoplamento estreito entre o manipulador e o requisito e só é recomendado para requisitos e manipuladores simples. Criar uma classe que implementa ambas as interfaces elimina a necessidade de registrar o manipulador em DI por causa do built-in PassThroughAuthorizationHandler que permite que os requisitos manipulem a si mesmos.

Consulte a AssertionRequirement classe para obter um bom exemplo onde 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 sucesso ou fracasso?

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

  • Um manipulador não precisa lidar com falhas em geral, pois outros manipuladores para o mesmo requisito podem ter êxito.

  • Para garantir falhas, mesmo que outros manipuladores de requisitos sejam bem-sucedidos, 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 o registro, que ocorre mesmo que outro manipulador tenha validado com êxito ou falhado em um requisito. Quando definida como false, a propriedade InvokeHandlersAfterFailure curto-circuita a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure define-se como padrão true, caso em que todos os manipuladores são chamados.

Note

Os manipuladores de autorização são chamados mesmo quando a autenticação falha.

Por que gostaria de ter vários gestores para um requisito?

Nos casos em que você deseja que a avaliação seja baseada em OR, implemente vários manipuladores para um único requisito. Por exemplo, a Microsoft tem portas que só abrem com cartões de chave. Se você deixar seu cartão-chave em casa, a rececionista 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;
    }
}

Certifique-se de que ambos os manipuladores estejam registrados. Se algum dos manipuladores for bem-sucedido quando uma política avalia o BuildingEntryRequirement, a avaliação da política é bem-sucedida.

Use uma função para implementar uma política

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

Por exemplo, o anterior BadgeEntryHandler pode ser reescrito da seguinte forma:

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 em manipuladores

O HandleRequirementAsync método que você implementa em um manipulador de autorização tem dois parâmetros: an AuthorizationHandlerContext e o TRequirement que você está manipulando. Frameworks como 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 é geralmente tratada pelo Middleware de Autorização. Neste caso, a Resource propriedade é uma instância de HttpContext. O contexto pode ser usado para acessar o ponto de extremidade atual, que pode ser usado para investigar o recurso subjacente para o qual você está roteando. 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. Esta propriedade fornece acesso a HttpContext, RouteData, e a tudo o mais fornecido pelo MVC e pelas Páginas Razor.

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

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

Globalmente exigem 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.