Autorização baseada em recursos no ASP.NET Core MVC

Este artigo descreve como autorizar utilizadores a aceder a recursos da aplicação.

Numa aplicação, um recurso é tipicamente representado por uma classe C# que inclui dados armazenados numa coleção, como um byte[] array. A classe normalmente contém metadados adicionais relacionados com o recurso, como um identificador único de recurso, datas, autores, informação de origem e um nome amigável para exibição numa interface. A coleção que contém dados de recursos é geralmente carregada a partir de conteúdos físicos de ficheiros, de um objeto de armazenamento na cloud, de um objeto em memória ou de dados de uma base de dados.

A autorização baseada em recursos requer atenção especial nas aplicações ASP.NET Core. A avaliação de atributos ocorre antes da ligação de dados e antes da execução de uma ação que carrega um recurso. A autorização declarativa com um [Authorize] atributo não é suficiente para autorização baseada em recursos. Em vez disso, a aplicação deve invocar um método de autorização personalizado — uma abordagem conhecida como autorização imperativa.

Visualize ou baixe o código de exemplo (como fazer o download).

Criar um aplicativo ASP.NET Core com dados do usuário protegidos por autorização contém um aplicativo de exemplo que usa autorização baseada em recursos.

Exemplos neste artigo utilizam construtores primários, disponíveis em C# 12 (.NET 8) ou posteriores. Para mais informações, consulte Declarar construtores primários para classes e estruturas (tutorial de documentação C#) e Construtores primários (Guia C#). Aplicações de exemplo que acompanham o artigo e que visam versões do .NET anteriores ao .NET 8 utilizam injeção de construtores.

Use uma autorização imperativa

A autorização é implementada como um IAuthorizationService, que é registado na coleção de serviços no arranque da aplicação pelo framework ASP.NET Core. O serviço é disponibilizado a classes e ações através de injeção de dependências. O controlador seguinte também injeta um repositório de documentos, que o programador cria e regista no contentor de serviço para gerir as operações do documento:

public class DocumentController(IAuthorizationService authorizationService,
    IDocumentRepository documentRepository) : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
        IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

    ...
}

IAuthorizationService tem duas sobrecargas do método AuthorizeAsync. Uma das sobrecargas aceita um recurso e um nome de política:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource, 
    string policyName);

A outra sobrecarga aceita um recurso e uma coleção de requisitos (IAuthorizationRequirement) para avaliar:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource,
    IEnumerable<IAuthorizationRequirement> requirements);

No exemplo seguinte, o recurso protegido é carregado num objeto personalizado Document . É invocada uma AuthorizeAsync sobrecarga para determinar se o utilizador atual pode editar o documento através de uma política de autorização personalizada.EditPolicy Se authorizationResult.Succeeded for igual a true, o utilizador está autorizado para o documento porque é o autor do documento (Document.Author corresponde ao Name do utilizador).

Note

O exemplo seguinte assume autenticação bem-sucedida com o conjunto de User propriedades.

[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = _documentRepository.Find(documentId);

    ...

    var authorizationResult = await _authorizationService
        .AuthorizeAsync(User, document, "EditPolicy");

    ...
}

Crie um handler baseado em recursos

Criar um gestor de autorização baseado em recursos é semelhante a criar um gestor de requisitos simples. Crie uma classe de requisitos personalizada e implemente uma classe handler de requisitos. Para mais informações sobre a criação de uma classe de requisitos, consulte a Autorização baseada em políticas: Requisitos.

A classe handler especifica o requisito e o tipo de recurso. O exemplo seguinte demonstra um manipulador a utilizar um SameAuthorRequirement requisito e um Document recurso:

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

No exemplo anterior, imagine que SameAuthorRequirement é um caso especial de uma classe SpecificAuthorRequirement mais genérica. A classe SpecificAuthorRequirement (não mostrada) contém uma propriedade Name que representa o nome do autor. A propriedade Name pode ser atribuída ao utilizador atual.

Registre o requisito e o manipulador em Program.cs:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));

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

Registre o requisito e o manipulador em Startup.ConfigureServices:

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Para mais informações sobre a criação de políticas de autorização, consulte Autorização baseada em políticas em ASP.NET Core.

Requisitos operacionais

Para tomar decisões com base nos resultados das operações CRUD (Crear, Lê, Atualizar, Eliminar), use a OperationAuthorizationRequirement classe auxiliar. Essa classe permite que você escreva um único manipulador em vez de uma classe individual para cada tipo de operação. Para usá-lo, forneça alguns nomes de operação:

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

O manipulador é implementado da seguinte forma, usando um requisito de OperationAuthorizationRequirement e um recurso Document:

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

O manipulador anterior valida a operação usando o recurso, a identidade do usuário e a propriedade Name do requisito.

Imponha restrições e proíba com um manipulador de recursos de operação

Esta secção mostra como são processados os resultados das ações de desafiar e proibir, e como estas se diferenciam.

Quando a autorização falha mas o utilizador está autenticado, a aplicação pode devolver um ForbidResult, que informa o middleware de autenticação de que a autorização falhou. Devolver a ChallengeResult para utilizadores não autenticados. Para clientes de navegador interativos, pode ser apropriado redirecionar o usuário para uma página de login.

Note

O exemplo seguinte assume autenticação bem-sucedida com o conjunto de User propriedades.

if ((await _authorizationService
    .AuthorizeAsync(User, document, Operations.Read)).Succeeded)
{
    return View(document);
}
else if (User.Identity?.IsAuthenticated ?? false)
{
    return new ForbidResult();
}
else
{
    return new ChallengeResult();
}