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

Uma política de autorização ASP.NET Core é um conjunto de um ou mais requisitos de autorização que a estrutura avalia para decidir se um usuário tem permissão para acessar um recurso. Uma política pode ser registrada com um nome e aplicada aos recursos que a exigem.

Este artigo explica:

  • Como exigir usuários autenticados por padrão.
  • Como criar requisitos.
  • Como registrar e aplicar políticas.
  • Como as políticas nomeadas, padrão e de contingência são selecionadas.
  • Manipuladores de autorização para avaliação de requisitos únicos e múltiplos.
  • Como vários requisitos em uma única política são avaliados.

Uma política nomeada é aplicada com [Authorize(Policy = "...")] (Razor componentes, páginas e controladores) ouRequireAuthorization(...) (pontos de extremidade), e o framework usa manipuladores para avaliar os requisitos por trás de uma política. IAuthorizationPolicyProvider(Provedores de política de autorização personalizados na documentação ASP.NET Core) geram políticas dinamicamente em vez de registrá-las na inicialização do aplicativo.

Autorização baseada em função e autorização baseada em declarações usam um requisito, um manipulador de requisitos e uma política de autorização pré-configurada. Esses blocos de construção dão suporte à expressão de avaliações de autorização no código.

Este artigo usa exemplos de componentes Razor e se concentra em cenários de autorização Blazor para o ASP.NET Core 3.1 ou versão posterior. Para orientações sobre Razor Pages e MVC aplicáveis ​​a todas as versões do ASP.NET Core, consulte os seguintes recursos após ler este artigo:

Alguns exemplos neste artigo (ASP.NET Core 8.0 ou posterior) usam construtores primários, disponíveis no C# 12 (.NET 8) ou posterior. Para obter mais informações, consulte Declarar construtores primários para classes e structs (tutorial de documentação do C#) e construtores primários (Guia do C#).

Exigir autenticação de usuário global

Para um aplicativo do lado do servidor em que a maioria ou todos os pontos de extremidade exigem autenticação, defina uma política de fallback que exija um usuário autenticado. Essa abordagem com segurança por padrão protege endpoints adicionados recentemente que não especificam metadados de autorização.

A política de fallback, não a política padrão, aplica-se a endpoints que não especificam metadados de autorização. A política padrão se aplica quando um endpoint a seleciona com [Authorize] ou RequireAuthorization() sem especificar um nome de política. Para ver as regras completas de seleção de políticas, consulte a seção Políticas padrão e de fallback.

var requireAuthPolicy = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();

builder.Services.AddAuthorizationBuilder()
    .SetFallbackPolicy(requireAuthPolicy);
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Em Startup.ConfigureServices:

services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Aplique [AllowAnonymous] ou chame AllowAnonymous() para pontos de extremidade que são intencionalmente públicos.

A política de fallback se aplica a solicitações processadas pelo middleware de autorização. Por exemplo:

  • Uma solicitação que não corresponde a um endpoint usa a política de fallback se o middleware de autorização for executado para essa solicitação.
  • Os arquivos estáticos servidos pelo middleware de arquivo estático antes do middleware de autorização não são protegidos pela política de fallback.
  • Os pontos de acesso públicos podem depender de recursos estáticos que também devem permitir acesso anônimo.

Para obter mais informações, consulte arquivos estáticos em ASP.NET Core e padrões de autorização de aplicativo do lado Blazor do servidor. Blazor WebAssembly os aplicativos não dão suporte a uma política de autorização de fallback no servidor. Para Blazor WebAssembly padrões de autorização, consulte Secure ASP.NET Core Blazor WebAssembly.

Políticas padrão e de fallback

O middleware de autorização combina os metadados de autorização de um endpoint em uma política. A tabela a seguir descreve como os metadados determinam qual política é usada:

Metadados de autorização Política ou comportamento
None O AuthorizationOptions.FallbackPolicy é usado, se estiver configurado. Por padrão, a política de fallback é null, portanto, a autorização não é necessária.
[Authorize] ou RequireAuthorization() sem um nome de política O AuthorizationOptions.DefaultPolicy é usado, a menos que o endpoint também tenha uma instância AuthorizationPolicy explícita. Por padrão, a política padrão requer um usuário autenticado.
[Authorize(Policy = "{POLICY NAME}")] ou RequireAuthorization("{POLICY NAME}") A política especificada é usada.
[Authorize(Roles = "{ROLES}")] Uma política é criada com as funções especificadas. A política padrão não é adicionada para essa declaração de autorização.
RequireAuthorization(policy) com uma instância de AuthorizationPolicy A política explícita é usada. Se houver metadados de política explícita, os dados de autorização simples e os dados de autorização restritos ao esquema de autenticação não adicionam a política padrão.
[Authorize(AuthenticationSchemes = "{SCHEME}")] sem um nome de política ou funções O esquema de autenticação especificado é usado. A política padrão também é usada, a menos que o endpoint tenha uma instância explícita AuthorizationPolicy.
Vários atributos [Authorize] ou chamadas RequireAuthorization(...) de seleção de política As políticas nomeadas, funções, esquemas de autenticação e políticas explícitas selecionadas são combinadas. Dados de autorização nus adicionam a política padrão somente quando nenhuma instância explícita AuthorizationPolicy está presente. Todos os requisitos na política combinada devem ser bem-sucedidos. A política de contingência não é utilizada.
[AllowAnonymous] ou AllowAnonymous() O middleware de autorização não impõe uma falha de autorização para o ponto de extremidade. A autenticação ainda pode ser executada e preenchida HttpContext.User.

A política de fallback não é combinada com uma política nomeada ou padrão. No caso das declarações mostradas na tabela anterior, ela só é selecionada quando nenhuma política de autorização é gerada a partir dos metadados de autorização do endpoint. Por exemplo, [Authorize] usa a política padrão em vez da política de fallback e [Authorize(Policy = "{POLICY NAME}")] usa a política nomeada em vez da política de fallback.

Os requisitos fornecidos como metadados do endpoint por meio de IAuthorizationRequirementData são adicionados após a seleção da política. Se nenhum outro metadado de autorização produzir uma política, esses requisitos serão combinados com a política de fallback quando uma política de fallback for configurada.

No ASP.NET Core 8.0 até 10.0, os atributos que implementam IAuthorizationRequirementData são aplicados apenas em pontos de extremidade de API mínima e roteados. Para obter suporte a modelos de versão e hospedagem, consulte Políticas de autorização personalizadas com 'IAuthorizationRequirementData'.

Requisitos e registro de política

Uma política de autorização consiste em um ou mais requisitos, que são utilizados por uma política para avaliar a autorização para o principal de usuário atual. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia.

Quando um requisito não contém dados ou tem propriedades (parâmetros), ele atua como um marcador vazio para disparar um manipulador de autorização associado (IAuthorizationHandler) para a autorização de processamento (descrito em detalhes mais adiante neste artigo). Como o manipulador nesse caso depende inteiramente do contexto HTTP, das declarações do usuário ou dos dados de back-end para tomar uma decisão sobre o usuário que atende ao requisito, a própria classe de requisito não exige dados ou parâmetros internos. O requisito apenas informa ao framework qual regra avaliar.

Por exemplo, considere o seguinte requisito de idade mínima (MinimumAgeRequirement), que é implementado apenas como uma classe de marcador:

public class MinimumAgeRequirement : IAuthorizationRequirement { }

O requisito anterior é usado para criar uma política que confirme que o usuário está acima de uma idade específica que o manipulador verifica. Um AuthorizationHandler<MinimumAgeRequirement> inspeciona o AuthorizationHandlerContext.User. Se o usuário tiver uma declaração de data de nascimento que indique que ele tenha mais de uma determinada idade, o requisito será bem-sucedido. O objeto de requisito não requer nenhuma propriedade (parâmetros) nesse caso. O exemplo a seguir demonstra a implementação completa de um requisito de idade mínima que tem um parâmetro para definir a idade mínima.

Considere o seguinte MinimumAgeRequirement requisito, que descreve um único parâmetro, uma idade mínima, a ser avaliado para autorização do usuário:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
    public int MinimumAge { get; } = minimumAge;
}
using Microsoft.AspNetCore.Authorization;

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

    public int MinimumAge { get; }
}
using Microsoft.AspNetCore.Authorization;

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

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

Uma política é registrada como parte da configuração do serviço de autorização no arquivo Program do aplicativo ao chamar AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos.

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AtLeast21", policy => 
        policy.Requirements.Add(new MinimumAgeRequirement(21)));

Uma política é registrada como parte da configuração do serviço de autorização no arquivo Program do aplicativo ao chamar AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos:

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

Uma política é registrada como parte da configuração do serviço de autorização em Startup.ConfigureServices (Startup.cs) chamando AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos:

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

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 AND .

Aplicar políticas aos Razor componentes

Aplique políticas a Razor componentes usando o [Authorize] atributo com o nome da política:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]

Se várias políticas forem aplicadas, todas as políticas deverão ser aprovadas antes que o acesso seja concedido:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]

Aplicar políticas a pontos de extremidade

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

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

Aplicar políticas em aplicativos MVC e Razor Pages

Para obter diretrizes sobre como aplicar políticas em Razor aplicativos Pages e MVC, consulte os seguintes recursos:

Interface do serviço de autorização (IAuthorizationService)

IAuthorizationService é o principal responsável por determinar se a autorização foi bem-sucedida quando uma sobrecarga IAuthorizationService.AuthorizeAsync é chamada:

  • AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements): verifica se um usuário atende a um conjunto específico de requisitos de autorização para um recurso especificado.
  • AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName): verifica se um usuário atende a uma política de autorização específica para um recurso especificado.

Se um recurso não for necessário para a avaliação da política, null será passado como recurso.

Os métodos anteriores retornam um AuthorizationResult encapsulado em um Task.

Cada um IAuthorizationHandler é responsável por verificar se os requisitos são atendidos por meio de IAuthorizationHandler.HandleAsync. A AuthorizationHandlerContext classe contém as informações de autorização usadas pela IAuthorizationHandler implementação. IAuthorizationRequirement é uma interface de marcador sem métodos que serve como o mecanismo para acompanhar se a autorização foi bem-sucedida. Quando AuthorizationHandlerContext.Succeed é chamado com IAuthorizationRequirement, a política é atendida:

context.Succeed(requirement);

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 de AuthorizationHandler<TRequirement>, 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 gerenciador de idade mínima gerencia uma única exigência.

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

namespace BlazorWebAppAuthorization.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);

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

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

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

        return Task.CompletedTask;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

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

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

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

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

        return Task.CompletedTask;
    }
}
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
            // 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).Value);

        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

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

        // 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 possui uma claim de data de nascimento. 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 é invocado com o requisito atendido como seu único parâmetro.

Usar um gerenciador 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 Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.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;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

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

        // 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 PendingRequirements: uma propriedade que contém requisitos não marcados como bem-sucedidos. Para um requisito ReadPermission, o usuário deve ser um proprietário ou um responsável para acessar o recurso solicitado. Para um requisito EditPermission ou DeletePermission, ele deve ser um proprietário para acessar o recurso solicitado.

Registro do manipulador

Registre manipuladores na coleção de serviços durante a configuração. O exemplo a seguir registra um manipulador de idade mínima (MinimumAgeHandler) como um serviço singleton, mas um manipulador pode ser registrado usando qualquer um dos tempos de vida de serviço integrados:

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

É possível agrupar um requisito e um manipulador em uma única classe implementando IAuthorizationRequirement e IAuthorizationHandler. Esse agrupamento cria um acoplamento apertado 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 no contêiner de serviços devido ao PassThroughAuthorizationHandler interno, que permite que os requisitos tratem a si próprios.

Consulte a implementação da classe AssertionRequirement do ASP.NET Core como exemplo de um caso em que o AssertionRequirement é ao mesmo tempo um requisito e o manipulador em uma classe completamente autocontida. A API do framework AssertionRequirement permite validar o acesso por meio de expressões lambda inline, em vez de escrever classes separadas de requisito e de manipulador repetitivas.

Note

Os links da documentação para o código-fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o estágio atual de desenvolvimento da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

O que um manipulador deve retornar?

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

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

  • De modo geral, não se exige que um manipulador trate falhas, uma vez 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 o registro de logs, o que ocorre mesmo que outro manipulador valide ou rejeite o requisito com sucesso. Quando definida como false, a propriedade InvokeHandlersAfterFailure causa um curto-circuito na execução dos manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure tem como padrão true; nesse caso todos os manipuladores são chamados.

Note

Manipuladores de autorização são chamados mesmo se a autenticação falhar. Além disso, os manipuladores podem ser executados em qualquer ordem; portanto, não dependa da ordem de chamada dos manipuladores.

Por que eu iria querer vários manipuladores 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, suponha que a Contoso Corporation tenha portas que só abrem com cartões-chave. Se você deixar seu cartão de chave em casa, a recepcionista imprimirá um adesivo temporário e abrirá a porta para você. Nesse cenário, o aplicativo tem um único requisito, mas vários manipuladores, cada um examinando um único requisito.

Nas seguintes implementações de exemplo:

  • BuildingEntryRequirement é o requisito de acesso ao edifício.
  • BadgeEntryHandler (o indivíduo tem um selo) e TemporaryStickerHandler (o indivíduo tem um adesivo temporário) são manipuladores separados, cada um examinando um único requisito.

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs:

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

namespace BlazorWebAppAuthorization.Policies.Handlers;

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

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs:

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

namespace BlazorWebAppAuthorization.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://contososecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

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

        // 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.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

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

        // 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 qualquer um dos manipuladores obtiver êxito quando uma política avaliar o BuildingEntryRequirement, a avaliação da política será bem-sucedida.

Usar um Func para atender a uma política

Há situações em que o cumprimento de uma política é simples de expressar no código com um Func<AuthorizationHandlerContext, bool> delegado ao configurar uma política com o RequireAssertion construtor de políticas. Por exemplo, o anterior BadgeEntryHandler pode ser reescrito da seguinte maneira:

    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://contososecurity")));
});

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

Autorização por meio de um exemplo de serviço externo

A Autorização por meio de um exemplo de serviço externo (dotnet/AspNetCore.Docs.SamplesGitHub repositório) mostra como implementar requisitos de autorização adicionais com um serviço de autorização externo. A solução Contoso.API tem seu projeto protegido por Microsoft Entra ID. Uma verificação de autorização adicional do Contoso.Security.API project retorna uma carga que descreve se o aplicativo cliente Contoso.API pode invocar a API de GetWeather.

Configurar o exemplo

A demonstração a seguir depende do uso de NSwag (Swagger/OpenAPI) ou cURL em um shell de comando.

No projeto Contoso.Security.API, defina o marcador AllowedClients ({CLIENT ID}) como qualquer valor de teste de GUID (por exemplo, 00001111-aaaa-2222-bbbb-3333cccc4444):

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}"
  ]
}

Em um shell de comando aberto no projeto Contoso.API, use dotnet user-jwts para gerar um token de acesso com uma declaração appid para o ID do aplicativo cliente, que foi criado na etapa anterior (por exemplo, 00001111-aaaa-2222-bbbb-3333cccc4444).

dotnet user-jwts create --claim appid={GUID}

Exemplo:

dotnet user-jwts create --claim appid=00001111-aaaa-2222-bbbb-3333cccc4444

O resultado produz um token após "Token:" no console de comando.

New JWT saved with ID '{JWT ID}'.
Name: {USER}
Custom Claims: [appid=00001111-aaaa-2222-bbbb-3333cccc4444]

Token: {TOKEN}

Defina o valor do token (onde o marcador {TOKEN} aparece na saída anterior) para uso posterior.

Você pode decodificar o token em um decodificador online JWT, como jwt.ms, para ver seu conteúdo, revelando que ele contém uma declaração appid com o ID do aplicativo cliente:

{
  "alg": "HS256",
  "typ": "JWT"
}.{
  "unique_name": "{USER}",
  "sub": "{USER}",
  "jti": "14ed7729",
  "appid": "{CLIENT ID}",
  "aud": [
    "https://localhost:7250",
    "http://localhost:7251"
  ],
  "nbf": 1780660887,
  "exp": 1788609687,
  "iat": 1780660888,
  "iss": "dotnet-user-jwts"
}.[Signature]

Execute o comando novamente com um valor de IDappid do cliente () incorreto:

dotnet user-jwts create --claim appid=aaaabbbb-0000-cccc-1111-dddd2222eeee

Defina o valor do segundo token à parte.

Inicie ambos os projetos Contoso.API e Contoso.Security.API no Visual Studio ou com o comando dotnet watch em um shell de comando:

dotnet watch

Na interface do Swagger do projeto Contoso.API (https://localhost:7250/swagger/index.html), selecione o botão Autorizar.

Na janela Autorizações disponíveis: Bearer, insira o token de acesso. Selecione o botão Autorizar. Feche a janela de autorizações disponíveis .

Em default, selecione o botão Get para o endpoint /WeatherForecast. Selecione o botão Experimentar . Selecione o botão Executar .

A saída em Responses>Resposta do servidor>Corpo da resposta mostra o JSON de previsão do tempo retornado pelo Contoso.API projeto.

Execute as mesmas etapas com o token de acesso que foi gerado com uma ID de aplicativo cliente inválida. A resposta é 403 – Proibido.

Recursos adicionais