Autorización basada en recursos en ASP.NET Core

El enfoque de autorización depende del recurso. Por ejemplo, solo el autor de un documento está autorizado para actualizar el documento. Por lo tanto, el documento debe recuperarse del almacén de datos antes de que se pueda realizar la evaluación de autorización.

La evaluación de atributos se produce antes del enlace de datos y antes de la ejecución del controlador de páginas o la acción que carga el documento. Por estas razones, la autorización declarativa con un atributo [Authorize] no es suficiente. En su lugar, puede invocar un método de autorización personalizado, un estilo conocido como autorización imperativa.

Vea o descargue el código de ejemplo (cómo descargarlo).

Creación de una aplicación de ASP.NET Core con datos de usuario protegidos por autorización contiene una aplicación de ejemplo que usa la autorización basada en recursos.

Uso de la autorización imperativa

La autorización se implementa como un servicio IAuthorizationService y se registra en la colección de servicios al iniciar la aplicación. El servicio se pone a disposición mediante inyección de dependencias a los controladores de página o acciones.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

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

IAuthorizationService tiene dos sobrecargas de método AuthorizeAsync: una aceptando el recurso y el nombre de la directiva y el otro aceptando el recurso y una lista de requisitos que se van a evaluar.

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

En el ejemplo siguiente, el recurso que se va a proteger se carga en un objeto personalizado Document. Se invoca una sobrecarga de AuthorizeAsync para determinar si el usuario actual puede editar el documento proporcionado. Una directiva de autorización personalizada "EditPolicy" se tiene en cuenta en la decisión. Consulte Autorización basada en directivas personalizadas para más información sobre la creación de directivas de autorización.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Escritura de un controlador basado en recursos

Escribir un controlador para la autorización basada en recursos no es muy diferente de escribir un controlador de requisitos sin formato. Cree una clase de requisito personalizada e implemente una clase de controlador de requisitos. Para más información sobre cómo crear una clase de requisitos, vea Requisitos.

La clase de controlador especifica el requisito y el tipo de recurso. Por ejemplo, a continuación se muestra un manejador que utiliza un recurso SameAuthorRequirement y otro Document:

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

En el ejemplo anterior, imagine que SameAuthorRequirement es un caso especial de una clase SpecificAuthorRequirement más genérica. La clase SpecificAuthorRequirement (no mostrada) contiene una propiedad Name que representa el nombre del autor. La propiedad Name podría establecerse para el usuario actual.

Registre el requisito y el controlador en 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 operativos

Si toma decisiones basadas en los resultados de operaciones CRUD (Crear, Leer, Actualizar, Eliminar), use la clase auxiliar OperationAuthorizationRequirement. Esta clase permite escribir un único controlador en lugar de una clase individual para cada tipo de operación. Para usarlo, proporcione algunos nombres de operación:

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

El controlador se implementa de la siguiente manera, usando un requisito OperationAuthorizationRequirement y un 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;
    }
}

El controlador anterior valida la operación mediante el recurso, la identidad del usuario y la propiedad Name del requisito.

Desafío y prohibición con un controlador de recursos operativos

En esta sección se muestra cómo se procesan los resultados de las acciones de desafío y prohibición y cómo difieren el desafío y la prohibición.

Para llamar a un controlador de recursos operativos, especifique la operación al invocar AuthorizeAsync en el controlador de páginas o la acción. En el ejemplo siguiente se determina si el usuario autenticado puede ver el documento proporcionado.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Si la autorización se realiza correctamente, se devuelve la página para ver el documento. Si la autorización falla pero el usuario está autenticado, devolver ForbidResult informa a cualquier middleware de autenticación que la autorización falló. Se devuelve un ChallengeResult cuando debe realizarse la autenticación. En el caso de los clientes interactivos del explorador, puede ser adecuado redirigir al usuario a una página de inicio de sesión.

El enfoque de autorización depende del recurso. Por ejemplo, solo el autor de un documento está autorizado para actualizar el documento. Por lo tanto, el documento debe recuperarse del almacén de datos antes de que se pueda realizar la evaluación de autorización.

La evaluación de atributos se produce antes del enlace de datos y antes de la ejecución del controlador de páginas o la acción que carga el documento. Por estas razones, la autorización declarativa con un atributo [Authorize] no es suficiente. En su lugar, puede invocar un método de autorización personalizado, un estilo conocido como autorización imperativa.

Vea o descargue el código de ejemplo (cómo descargarlo).

Creación de una aplicación de ASP.NET Core con datos de usuario protegidos por autorización contiene una aplicación de ejemplo que usa la autorización basada en recursos.

Uso de la autorización imperativa

La autorización se implementa como un servicio IAuthorizationService y se registra en la colección de servicios dentro de la clase Startup. El servicio se pone a disposición mediante inyección de dependencias a los controladores de página o acciones.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

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

IAuthorizationService tiene dos sobrecargas de método AuthorizeAsync: una aceptando el recurso y el nombre de la directiva y el otro aceptando el recurso y una lista de requisitos que se van a evaluar.

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

En el ejemplo siguiente, el recurso que se va a proteger se carga en un objeto personalizado Document. Se invoca una sobrecarga de AuthorizeAsync para determinar si el usuario actual puede editar el documento proporcionado. Una directiva de autorización personalizada "EditPolicy" se tiene en cuenta en la decisión. Consulte Autorización basada en directivas personalizadas para más información sobre la creación de directivas de autorización.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Escritura de un controlador basado en recursos

Escribir un controlador para la autorización basada en recursos no es muy diferente de escribir un controlador de requisitos sin formato. Cree una clase de requisito personalizada e implemente una clase de controlador de requisitos. Para más información sobre cómo crear una clase de requisitos, vea Requisitos.

La clase de controlador especifica el requisito y el tipo de recurso. Por ejemplo, a continuación se muestra un manejador que utiliza un recurso SameAuthorRequirement y otro Document:

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

En el ejemplo anterior, imagine que SameAuthorRequirement es un caso especial de una clase SpecificAuthorRequirement más genérica. La clase SpecificAuthorRequirement (no mostrada) contiene una propiedad Name que representa el nombre del autor. La propiedad Name podría establecerse para el usuario actual.

Registre el requisito y el controlador en 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 operativos

Si toma decisiones basadas en los resultados de operaciones CRUD (Crear, Leer, Actualizar, Eliminar), use la clase auxiliar OperationAuthorizationRequirement. Esta clase permite escribir un único controlador en lugar de una clase individual para cada tipo de operación. Para usarlo, proporcione algunos nombres de operación:

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

El controlador se implementa de la siguiente manera, usando un requisito OperationAuthorizationRequirement y un 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;
    }
}

El controlador anterior valida la operación mediante el recurso, la identidad del usuario y la propiedad Name del requisito.

Desafío y prohibición con un controlador de recursos operativos

En esta sección se muestra cómo se procesan los resultados de las acciones de desafío y prohibición y cómo difieren el desafío y la prohibición.

Para llamar a un controlador de recursos operativos, especifique la operación al invocar AuthorizeAsync en el controlador de páginas o la acción. En el ejemplo siguiente se determina si el usuario autenticado puede ver el documento proporcionado.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Si la autorización se realiza correctamente, se devuelve la página para ver el documento. Si la autorización falla pero el usuario está autenticado, devolver ForbidResult informa a cualquier middleware de autenticación que la autorización falló. Se devuelve un ChallengeResult cuando debe realizarse la autenticación. En el caso de los clientes interactivos del explorador, puede ser adecuado redirigir al usuario a una página de inicio de sesión.

El enfoque de autorización depende del recurso. Por ejemplo, solo el autor de un documento está autorizado para actualizar el documento. Por lo tanto, el documento debe recuperarse del almacén de datos antes de que se pueda realizar la evaluación de autorización.

La evaluación de atributos se produce antes del enlace de datos y antes de la ejecución del controlador de páginas o la acción que carga el documento. Por estas razones, la autorización declarativa con un atributo [Authorize] no es suficiente. En su lugar, puede invocar un método de autorización personalizado, un estilo conocido como autorización imperativa.

Vea o descargue el código de ejemplo (cómo descargarlo).

Creación de una aplicación de ASP.NET Core con datos de usuario protegidos por autorización contiene una aplicación de ejemplo que usa la autorización basada en recursos.

Uso de la autorización imperativa

La autorización se implementa como un servicio IAuthorizationService y se registra en la colección de servicios dentro de la clase Startup. El servicio se pone a disposición mediante inyección de dependencias a los controladores de página o acciones.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

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

IAuthorizationService tiene dos sobrecargas de método AuthorizeAsync: una aceptando el recurso y el nombre de la directiva y el otro aceptando el recurso y una lista de requisitos que se van a evaluar.

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

En el ejemplo siguiente, el recurso que se va a proteger se carga en un objeto personalizado Document. Se invoca una sobrecarga de AuthorizeAsync para determinar si el usuario actual puede editar el documento proporcionado. Una directiva de autorización personalizada "EditPolicy" se tiene en cuenta en la decisión. Consulte Autorización basada en directivas personalizadas para más información sobre la creación de directivas de autorización.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Escritura de un controlador basado en recursos

Escribir un controlador para la autorización basada en recursos no es muy diferente de escribir un controlador de requisitos sin formato. Cree una clase de requisito personalizada e implemente una clase de controlador de requisitos. Para más información sobre cómo crear una clase de requisitos, vea Requisitos.

La clase de controlador especifica el requisito y el tipo de recurso. Por ejemplo, a continuación se muestra un manejador que utiliza un recurso SameAuthorRequirement y otro Document:

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

En el ejemplo anterior, imagine que SameAuthorRequirement es un caso especial de una clase SpecificAuthorRequirement más genérica. La clase SpecificAuthorRequirement (no mostrada) contiene una propiedad Name que representa el nombre del autor. La propiedad Name podría establecerse para el usuario actual.

Registre el requisito y el controlador en 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 operativos

Si toma decisiones basadas en los resultados de operaciones CRUD (Crear, Leer, Actualizar, Eliminar), use la clase auxiliar OperationAuthorizationRequirement. Esta clase permite escribir un único controlador en lugar de una clase individual para cada tipo de operación. Para usarlo, proporcione algunos nombres de operación:

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

El controlador se implementa de la siguiente manera, usando un requisito OperationAuthorizationRequirement y un 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;
    }
}

El controlador anterior valida la operación mediante el recurso, la identidad del usuario y la propiedad Name del requisito.

Desafío y prohibición con un controlador de recursos operativos

En esta sección se muestra cómo se procesan los resultados de las acciones de desafío y prohibición y cómo difieren el desafío y la prohibición.

Para llamar a un controlador de recursos operativos, especifique la operación al invocar AuthorizeAsync en el controlador de páginas o la acción. En el ejemplo siguiente se determina si el usuario autenticado puede ver el documento proporcionado.

Nota:

Los siguientes ejemplos de código suponen que se ha ejecutado la autenticación y establecido la propiedad 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();
    }
}

Si la autorización se realiza correctamente, se devuelve la página para ver el documento. Si la autorización falla pero el usuario está autenticado, devolver ForbidResult informa a cualquier middleware de autenticación que la autorización falló. Se devuelve un ChallengeResult cuando debe realizarse la autenticación. En el caso de los clientes interactivos del explorador, puede ser adecuado redirigir al usuario a una página de inicio de sesión.