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

A abordagem de autorização depende do recurso. Por exemplo, somente o autor de um documento está autorizado a atualizar o documento. Consequentemente, o documento deve ser recuperado do armazenamento de dados antes que a avaliação de autorização ocorra.

A avaliação do atributo ocorre antes da associação de dados e antes da execução do manipulador de página ou da ação que carrega o documento. Por esses motivos, a autorização declarativa com um atributo [Authorize] não é suficiente. Em vez disso, você pode invocar um método de autorização personalizado, ou seja, um estilo conhecido como autorização imperativa.

Exibir ou baixar um código de exemplo (como baixar).

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

Usar autorização imperativa

A autorização é implementada como um serviço IAuthorizationService e é registrada na coleção de serviços na inicialização do aplicativo. O serviço é disponibilizado por meio da injeção de dependência para manipuladores de página ou ações.

public class DocumentController : 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 aceitando o recurso e o nome da política e a outra aceitando o recurso e uma lista de requisitos a serem avaliados.

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

No exemplo a seguir, o recurso a ser protegido é carregado em um objeto personalizado Document . Uma sobrecarga AuthorizeAsync é invocada para determinar se o usuário atual tem permissão para editar o documento fornecido. Uma política de autorização "EditPolicy" personalizada é fatorada na decisão. Confira o tópico Autorização baseada em política personalizada para obter mais informações sobre como criar políticas de autorização.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

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

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Escrever um manipulador baseado em recursos

Escrever um manipulador para autorização baseada em recursos não é muito diferente de escrever um manipulador de requisitos sem formatação. Crie uma classe de requisito personalizada e implemente uma classe de manipulador de requisitos. Para obter mais informações sobre como criar uma classe de requisitos, confira Requisitos.

A classe de manipulador especifica o requisito e o tipo de recurso. Por exemplo, um manipulador que utiliza um recurso SameAuthorRequirement e Document segue:

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 definida como o usuário atual.

Registre o requisito e o manipulador em Program.cs:

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

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

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
builder.Services.AddScoped<IDocumentRepository, DocumentRepository>();

Requisitos operacionais

Se você estiver tomando decisões com base nos resultados das operações CRUD (Criar, Ler, Atualizar, Excluir), use a classe auxiliar OperationAuthorizationRequirement. Essa classe permite que você escreva um único manipulador em vez de uma classe individual para cada tipo de operação. Para usá-la, 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 maneira, usando um requisito 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 identity do usuário e a propriedade Name do requisito.

Desafio e proibição com um manipulador de recursos operacional

Esta seção mostra como os resultados da ação de desafio e proibição são processados e como o desafio e a proibição diferem.

Para chamar um manipulador de recursos operacional, especifique a operação ao invocar AuthorizeAsync em seu manipulador de página ou ação. O exemplo a seguir determina se o usuário autenticado tem permissão para exibir o documento fornecido.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Se a autorização for bem-sucedida, a página para visualização do documento será retornada. Se a autorização falhar, mas o usuário for autenticado, o retorno ForbidResult informará a qualquer middleware de autenticação de que a autorização falhou. Um ChallengeResult é retornado quando a autenticação deve ser executada. Para clientes de navegadores interativos, talvez seja apropriado redirecionar o usuário para uma página de login.

A abordagem de autorização depende do recurso. Por exemplo, somente o autor de um documento está autorizado a atualizar o documento. Consequentemente, o documento deve ser recuperado do armazenamento de dados antes que a avaliação de autorização ocorra.

A avaliação do atributo ocorre antes da associação de dados e antes da execução do manipulador de página ou da ação que carrega o documento. Por esses motivos, a autorização declarativa com um atributo [Authorize] não é suficiente. Em vez disso, você pode invocar um método de autorização personalizado, ou seja, um estilo conhecido como autorização imperativa.

Exibir ou baixar um código de exemplo (como baixar).

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

Usar autorização imperativa

A autorização é implementada como um serviço IAuthorizationService e é registrada na coleção de serviços dentro da classe Startup. O serviço é disponibilizado por meio da injeção de dependência para manipuladores de página ou ações.

public class DocumentController : 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 aceitando o recurso e o nome da política e a outra aceitando o recurso e uma lista de requisitos a serem avaliados.

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

No exemplo a seguir, o recurso a ser protegido é carregado em um objeto personalizado Document . Uma sobrecarga AuthorizeAsync é invocada para determinar se o usuário atual tem permissão para editar o documento fornecido. Uma política de autorização "EditPolicy" personalizada é fatorada na decisão. Confira o tópico Autorização baseada em política personalizada para obter mais informações sobre como criar políticas de autorização.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

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

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Escrever um manipulador baseado em recursos

Escrever um manipulador para autorização baseada em recursos não é muito diferente de escrever um manipulador de requisitos sem formatação. Crie uma classe de requisito personalizada e implemente uma classe de manipulador de requisitos. Para obter mais informações sobre como criar uma classe de requisitos, confira Requisitos.

A classe de manipulador especifica o requisito e o tipo de recurso. Por exemplo, um manipulador que utiliza um recurso SameAuthorRequirement e Document segue:

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 definida como o usuário atual.

Registre o requisito e o manipulador em Startup.ConfigureServices:

services.AddControllersWithViews();
services.AddRazorPages();

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

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

Requisitos operacionais

Se você estiver tomando decisões com base nos resultados das operações CRUD (Criar, Ler, Atualizar, Excluir), use a classe auxiliar OperationAuthorizationRequirement. Essa classe permite que você escreva um único manipulador em vez de uma classe individual para cada tipo de operação. Para usá-la, 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 maneira, usando um requisito 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 identity do usuário e a propriedade Name do requisito.

Desafio e proibição com um manipulador de recursos operacional

Esta seção mostra como os resultados da ação de desafio e proibição são processados e como o desafio e a proibição diferem.

Para chamar um manipulador de recursos operacional, especifique a operação ao invocar AuthorizeAsync em seu manipulador de página ou ação. O exemplo a seguir determina se o usuário autenticado tem permissão para exibir o documento fornecido.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Se a autorização for bem-sucedida, a página para visualização do documento será retornada. Se a autorização falhar, mas o usuário for autenticado, o retorno ForbidResult informará a qualquer middleware de autenticação de que a autorização falhou. Um ChallengeResult é retornado quando a autenticação deve ser executada. Para clientes de navegadores interativos, talvez seja apropriado redirecionar o usuário para uma página de login.

A abordagem de autorização depende do recurso. Por exemplo, somente o autor de um documento está autorizado a atualizar o documento. Consequentemente, o documento deve ser recuperado do armazenamento de dados antes que a avaliação de autorização ocorra.

A avaliação do atributo ocorre antes da associação de dados e antes da execução do manipulador de página ou da ação que carrega o documento. Por esses motivos, a autorização declarativa com um atributo [Authorize] não é suficiente. Em vez disso, você pode invocar um método de autorização personalizado, ou seja, um estilo conhecido como autorização imperativa.

Exibir ou baixar um código de exemplo (como baixar).

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

Usar autorização imperativa

A autorização é implementada como um serviço IAuthorizationService e é registrada na coleção de serviços dentro da classe Startup. O serviço é disponibilizado por meio da injeção de dependência para manipuladores de página ou ações.

public class DocumentController : 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 aceitando o recurso e o nome da política e a outra aceitando o recurso e uma lista de requisitos a serem avaliados.

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

No exemplo a seguir, o recurso a ser protegido é carregado em um objeto personalizado Document . Uma sobrecarga AuthorizeAsync é invocada para determinar se o usuário atual tem permissão para editar o documento fornecido. Uma política de autorização "EditPolicy" personalizada é fatorada na decisão. Confira o tópico Autorização baseada em política personalizada para obter mais informações sobre como criar políticas de autorização.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

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

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Escrever um manipulador baseado em recursos

Escrever um manipulador para autorização baseada em recursos não é muito diferente de escrever um manipulador de requisitos sem formatação. Crie uma classe de requisito personalizada e implemente uma classe de manipulador de requisitos. Para obter mais informações sobre como criar uma classe de requisitos, confira Requisitos.

A classe de manipulador especifica o requisito e o tipo de recurso. Por exemplo, um manipulador que utiliza um recurso SameAuthorRequirement e Document segue:

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 definida como o usuário atual.

Registre o requisito e o manipulador em Startup.ConfigureServices:

services.AddMvc();

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

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

Requisitos operacionais

Se você estiver tomando decisões com base nos resultados das operações CRUD (Criar, Ler, Atualizar, Excluir), use a classe auxiliar OperationAuthorizationRequirement. Essa classe permite que você escreva um único manipulador em vez de uma classe individual para cada tipo de operação. Para usá-la, 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 maneira, usando um requisito 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 identity do usuário e a propriedade Name do requisito.

Desafio e proibição com um manipulador de recursos operacional

Esta seção mostra como os resultados da ação de desafio e proibição são processados e como o desafio e a proibição diferem.

Para chamar um manipulador de recursos operacional, especifique a operação ao invocar AuthorizeAsync em seu manipulador de página ou ação. O exemplo a seguir determina se o usuário autenticado tem permissão para exibir o documento fornecido.

Observação

Os exemplos de código a seguir pressupõem que a autenticação foi executada e definiu a propriedade User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Se a autorização for bem-sucedida, a página para visualização do documento será retornada. Se a autorização falhar, mas o usuário for autenticado, o retorno ForbidResult informará a qualquer middleware de autenticação de que a autorização falhou. Um ChallengeResult é retornado quando a autenticação deve ser executada. Para clientes de navegadores interativos, talvez seja apropriado redirecionar o usuário para uma página de login.