Autorización basada en recursos en ASP.NET Core
La estrategia de autorización depende del recurso al que se accede. Considere un documento que tiene una propiedad author. Solo el autor puede 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ágina o la acción que carga el documento. Por estos motivos, la autorización declarativa con [Authorize] un atributo no es suficiente. En su lugar, puede invocar a un método de autorización personalizado — un estilo conocido como autorización imperativa.
Crear una aplicación 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 Startup clase . El servicio está disponible a través de la inserción de dependencias en 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: una que acepta el recurso y el nombre de la directiva y la otra que acepta el recurso y una lista de requisitos que AuthorizeAsync se deben evaluar.
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);
En el ejemplo siguiente, el recurso que se va a proteger se carga en un objeto Document personalizado. Se AuthorizeAsync invoca una sobrecarga para determinar si el usuario actual puede editar el documento proporcionado. Una directiva de autorización "EditPolicy" personalizada se tiene en cuenta en la decisión. Consulte Autorización basada en directivas personalizadas para obtener más información sobre la creación de directivas de autorización.
Nota
En los ejemplos de código siguientes se supone que la autenticación se ha ejecutado y establecido la User propiedad .
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();
}
}
[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, "EditPolicy"))
{
return View(document);
}
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 obtener más información sobre cómo crear una clase de requisito, vea Requirements.
La clase de controlador especifica el requisito y el tipo de recurso. Por ejemplo, a continuación se muestra un SameAuthorRequirement controlador que utiliza y un 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 { }
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);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
public class SameAuthorRequirement : IAuthorizationRequirement { }
En el ejemplo anterior, imagine que SameAuthorRequirement es un caso especial de una clase más SpecificAuthorRequirement genérica. La SpecificAuthorRequirement clase (no mostrada) contiene una Name propiedad que representa el nombre del autor. La Name propiedad se puede establecer en 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>();
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>();
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 las operaciones CRUD (Crear, Leer, Actualizar, Eliminar), use la clase auxiliar OperationAuthorizationRequirement. Esta clase permite escribir un solo 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 como se muestra a continuación, mediante OperationAuthorizationRequirement un requisito y un Document recurso:
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;
}
}
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);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
El controlador anterior valida la operación mediante el recurso, la identidad del usuario y la propiedad del Name 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 la acción 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 en AuthorizeAsync el controlador de página o la acción. En el ejemplo siguiente se determina si el usuario autenticado puede ver el documento proporcionado.
Nota
En los ejemplos de código siguientes se supone que la autenticación se ha ejecutado y establecido la User propiedad .
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 se produce un error en la autorización pero el usuario está autenticado, la devolución informa a ForbidResult cualquier middleware de autenticación de que se ha producido un error en la autorización. Se ChallengeResult devuelve cuando se debe realizar la autenticación. Para los clientes de explorador interactivo, puede ser adecuado redirigir al usuario a una página de inicio de sesión.
[HttpGet]
public async Task<IActionResult> View(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, Operations.Read))
{
return View(document);
}
else
{
return new ChallengeResult();
}
}
Si la autorización se realiza correctamente, se devuelve la vista del documento. Si se produce un error en la autorización, la devolución informa a cualquier middleware de autenticación de que se ha produce un error de autorización y el ChallengeResult middleware puede tomar la respuesta adecuada. Una respuesta adecuada podría ser devolver un código de estado 401 o 403. Para los clientes de explorador interactivo, podría significar redirigir al usuario a una página de inicio de sesión.