Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
De manera oculta, la autorización basada en roles y la autorización basada en notificaciones usan un requisito, un controlador de requisitos y una directiva preconfigurada. Estos bloques de creación admiten la expresión de evaluaciones de autorización en el código. El resultado es una estructura de autorización más completa, reutilizable y verificable.
Una directiva de autorización consta de uno o varios requisitos. Regístrelo como parte de la configuración del servicio de autorización, en el archivo de la aplicación:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
En el ejemplo anterior, se crea una política "AtLeast21". Tiene un único requisito: el de una edad mínima, que se proporciona como parámetro para el requisito.
IAuthorizationService
El servicio principal que determina si la autorización se realiza correctamente es :
/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Checks if a user meets a specific set of requirements for the specified resource
/// </summary>
/// <param name="user">The user to evaluate the requirements against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="requirements">The requirements to evaluate.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// This value is <value>true</value> when the user fulfills the policy;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
IEnumerable<IAuthorizationRequirement> requirements);
/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="policyName">The name of the policy to check against a specific
/// context.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// Returns a flag indicating whether the user, and optional resource has fulfilled
/// the policy.
/// <value>true</value> when the policy has been fulfilled;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user, object resource, string policyName);
}
El código anterior resalta los dos métodos del IAuthorizationService.
es una interfaz de marcador sin métodos y un mecanismo para rastrear si la autorización es exitosa.
Cada es responsable de comprobar si se cumplen los requisitos:
/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization information.</param>
Task HandleAsync(AuthorizationHandlerContext context);
}
La clase es la que usa el controlador para marcar si se han cumplido los requisitos:
context.Succeed(requirement)
En el código siguiente se muestra la implementación predeterminada simplificada (y anotada con comentarios) del servicio de autorización:
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
// Create a tracking context from the authorization inputs.
var authContext = _contextFactory.CreateContext(requirements, user, resource);
// By default this returns an IEnumerable<IAuthorizationHandler> from DI.
var handlers = await _handlers.GetHandlersAsync(authContext);
// Invoke all handlers.
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
}
// Check the context, by default success is when all requirements have been met.
return _evaluator.Evaluate(authContext);
}
En el código siguiente se muestra una configuración típica del servicio de autorización:
// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...
builder.Services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
// Configure your policies
builder.Services.AddAuthorization(options =>
options.AddPolicy("Something",
policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
Use , o para la autorización.
Aplicación de directivas a controladores MVC
Para las aplicaciones que usan Pages, consulte la sección Aplicar directivas a Pages.
Aplique directivas a los controladores usando el atributo con el nombre de la directiva:
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
public IActionResult Index() => View();
}
Si se aplican varias directivas en los niveles de controlador y acción, todas las directivas deben pasar antes de que se conceda acceso.
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller2 : Controller
{
[Authorize(Policy = "IdentificationValidated")]
public IActionResult Index() => View();
}
Aplicar directivas a Pages
Aplique directivas a Pages usando el atributo con el nombre de la directiva. Por ejemplo:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationPoliciesSample.Pages;
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }
Las directivas no pueden aplicarse a nivel de controlador de Página de , deben aplicarse a la página.
Las directivas también se pueden aplicar a Pages mediante una convención de autorización.
Aplicación de directivas a puntos de conexión
Aplique las directivas a los puntos de conexión usando con el nombre de la directiva. Por ejemplo:
app.MapGet("/helloworld", () => "Hello World!")
.RequireAuthorization("AtLeast21");
Requirements
Un requisito de autorización es una colección de parámetros de datos que una directiva puede usar para evaluar la entidad de seguridad de usuario actual. En nuestra directiva "AtLeast21", el requisito es un único parámetro: la edad mínima. Un requisito implementa , que es una interfaz de marcador vacía. Un requisito de edad mínima parametrizada podría implementarse de la siguiente manera:
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Requirements;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int minimumAge) =>
MinimumAge = minimumAge;
public int MinimumAge { get; }
}
Si una directiva de autorización contiene varios requisitos de autorización, todos los requisitos deben pasar para que la evaluación de la directiva se realice correctamente. En otras palabras, varios requisitos de autorización agregados a una sola directiva de autorización se consideran en base a una lógica AND.
Note
Un requisito no necesita tener datos ni propiedades.
Controladores de autorización
Un controlador de autorización es responsable de la evaluación de las propiedades de un requisito. El controlador de autorización evalúa los requisitos con respecto a un AuthorizationHandlerContext proporcionado para determinar si se permite el acceso.
Un requisito puede tener varios controladores. Un controlador puede heredar , donde es el requisito que se va a controlar. Como alternativa, un controlador puede implementar directamente para controlar más de un tipo de requisito.
Uso de un controlador para un requisito
En el ejemplo siguiente se muestra una relación uno a uno en la que un controlador de edad mínima controla un único requisito:
using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.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 && c.Issuer == "http://contoso.com");
if (dateOfBirthClaim is null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
El código anterior determina si la entidad de seguridad de usuario actual tiene una fecha de nacimiento emitida por un emisor conocido y de confianza. La autorización no se puede producir cuando falta la notificación, en cuyo caso se devuelve una tarea completada. Cuando hay una reclamación, se calcula la edad del usuario. Si el usuario cumple la edad mínima definida por el requisito, la autorización se considera correcta. Cuando la autorización se realiza correctamente, se invoca con el requisito satisfecho como único parámetro.
Uso de un controlador para varios requisitos
En el ejemplo siguiente se muestra una relación uno a varios en la que un controlador de permisos puede controlar tres tipos diferentes de requisitos:
using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.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;
}
}
El código anterior atraviesa : una propiedad que contiene requisitos no marcados como exitosos. Para un requisito de ReadPermission, el usuario debe ser el propietario o patrocinador para acceder al recurso solicitado. Para un requisito de EditPermission o DeletePermission, deben ser los propietarios para acceder al recurso solicitado.
Registro del controlador
Registre controladores en la colección de servicios durante la configuración. Por ejemplo:
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
El código anterior registra como un singleton. Los controladores se pueden registrar mediante cualquiera de las duraciones de servicio integradas.
Es posible agrupar un requisito y un controlador en una sola clase que implemente y . Esta agrupación crea un acoplamiento estricto entre el controlador y el requisito y solo se recomienda para los requisitos y controladores simples. La creación de una clase que implemente ambas interfaces elimina la necesidad de registrar el controlador en DI debido al incorporado que permite que los requisitos se controlen a sí mismos.
Consulte la implementación de la clase para obtener un buen ejemplo donde el es tanto un requisito como el controlador en una clase completamente autónoma.
¿Qué debe devolver un controlador?
Observe que el método del ejemplo del controlador no devuelve ningún valor. ¿Cómo se indica un estado de éxito o error?
Un controlador indica el éxito llamando a , pasándose el requisito que ha sido validado con éxito.
Por lo general, un controlador no necesita controlar los errores, ya que otros controladores para el mismo requisito pueden tener éxito.
Para garantizar el fracaso, incluso si otros gestores de requisitos tienen éxito, llame a .
Si un controlador llama a o , se sigue llamando a todos los demás controladores. Esto permite que los requisitos produzcan efectos secundarios, como el registro, que tiene lugar incluso si otro controlador ha validado correctamente o ha producido un error en un requisito. Cuando se establece en , la propiedad interrumpe la ejecución de controladores cuando se llama a . El valor predeterminado de es , en cuyo caso se llama a todos los controladores.
Note
Se llama a los controladores de autorización incluso si se produce un error en la autenticación. Además, los controladores pueden ejecutarse en cualquier orden, por lo que no dependen de que se les llame en un orden determinado.
¿Por qué necesitaría varios manejadores para un requisito?
En los casos en los que quiera que la evaluación sea en función de OR, implemente varios controladores para un único requisito. Por ejemplo, Microsoft tiene puertas que solo se abren con tarjetas llave. Si dejas tu tarjeta de llave en casa, el recepcionista imprime una pegatina temporal y abre la puerta para ti. En este escenario, tendría un único requisito, BuildingEntry, pero varios controladores, cada uno examinando un único requisito.
BuildingEntryRequirement.cs
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Requirements;
public class BuildingEntryRequirement : IAuthorizationRequirement { }
BadgeEntryHandler.cs
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.Policies.Handlers;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(
c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
TemporaryStickerHandler.cs
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
namespace AuthorizationPoliciesSample.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://microsoftsecurity"))
{
// Code to check expiration date omitted for brevity.
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Asegúrese de que ambos controladores están registrados. Si cualquiera de los controladores tiene éxito cuando una directiva evalúa , la evaluación de la directiva tiene éxito.
Usar una función para cumplir una directiva
Puede haber situaciones en las que el cumplimiento de una directiva es fácil de expresar en el código. Es posible proporcionar un al configurar una directiva con el generador de directivas .
Por ejemplo, el anterior podría reescribirse de la siguiente manera:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context => context.User.HasClaim(c =>
(c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
&& c.Issuer == "https://microsoftsecurity")));
});
Acceder al contexto de solicitud de MVC en manejadores
El método tiene dos parámetros: un y el que se está manejando. Frameworks como MVC o son libres de agregar cualquier objeto a la propiedad del para transmitir información adicional.
Al usar el enrutamiento de puntos de conexión, el middleware de autorización suele controlar la autorización. En este caso, la propiedad es una instancia de . El contexto se puede usar para acceder al punto de acceso actual, lo cual permite sondear el recurso subyacente al que se está dirigiendo. Por ejemplo:
if (context.Resource is HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
...
}
Con el enrutamiento tradicional, o cuando la autorización se produce como parte del filtro de autorización de MVC, el valor de es una instancia de . Esta propiedad proporciona acceso a HttpContext, RouteData y todo lo demás proporcionado por MVC y Razor Pages.
El uso de la propiedad es específico del marco. El uso de información en la propiedad limita las directivas de autorización a marcos concretos. Convierta la propiedad usando la palabra clave , y después confirme que la conversión se ha realizado con éxito para asegurarse de que su código no falle con un cuando se ejecute en otros frameworks.
// Requires the following import:
// using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
}
Requerir globalmente que todos los usuarios se autentiquen
Para obtener información sobre cómo requerir autenticación para todos los usuarios de la aplicación, consulte Crear una aplicación de ASP.NET Core con datos de usuario protegidos por autorización.
Ejemplo de autorización con servicio externo
El código de ejemplo de AspNetCore.Docs.Samples muestra cómo implementar requisitos de autorización adicionales con un servicio de autorización externo. El Contoso.API project de ejemplo está protegido con Azure AD. Una comprobación de autorización adicional del proyecto Contoso.Security.API devuelve una carga que describe si la aplicación cliente Contoso.API puede invocar la API GetWeather.
Configuración del ejemplo
Cree un registro de aplicación en el directorio de Microsoft Entra ID:
Asígnele un AppRole.
En Permisos de API, agregue AppRole como permiso y conceda el consentimiento de Administrador. Tenga en cuenta que, en esta configuración, este registro de aplicación representa tanto la API como el cliente que invoca la API. Si lo desea, puede crear dos registros de aplicaciones. Si usa esta configuración, asegúrese de realizar solo los permisos de API, agregue AppRole como paso de permiso solo para el cliente. Solo el registro de la aplicación cliente requiere que se genere un secreto de cliente.
Configure el
Contoso.APIproyecto con los valores siguientes:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<Tenant name from AAD properties>.onmicrosoft.com",
"TenantId": "<Tenant Id from AAD properties>",
"ClientId": "<Client Id from App Registration representing the API>"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
- Configura con los siguientes ajustes:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AllowedClients": [
"<Use the appropriate Client Id representing the Client calling the API>"
]
}
Abra el archivo ContosoAPI.collection.json y configure un entorno con lo siguiente:
- : Id. de cliente del registro de aplicaciones que representa al cliente que invoca la API.
- : secreto de cliente del registro de la aplicación que representa al cliente que llama a la API.
- : identificador de inquilino de las propiedades de AAD
Extraiga los comandos del archivo y úselos para construir comandos cURL a fin de probar la aplicación.
Ejecute la solución y use cURL para invocar la API. Puede agregar puntos de interrupción en y observar que se pasa el identificador de cliente que se usa para afirmar si se permite obtener tiempo.
Recursos adicionales
De manera oculta, la autorización basada en roles y la autorización basada en notificaciones usan un requisito, un controlador de requisitos y una directiva preconfigurada. Estos bloques de creación admiten la expresión de evaluaciones de autorización en el código. El resultado es una estructura de autorización más completa, reutilizable y verificable.
Una directiva de autorización consta de uno o varios requisitos. Se registra como parte de la configuración del servicio de autorización en el método :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
}
En el ejemplo anterior, se crea una política "AtLeast21". Tiene un único requisito: el de una edad mínima, que se proporciona como parámetro para el requisito.
IAuthorizationService
El servicio principal que determina si la autorización se realiza correctamente es :
/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Checks if a user meets a specific set of requirements for the specified resource
/// </summary>
/// <param name="user">The user to evaluate the requirements against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="requirements">The requirements to evaluate.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// This value is <value>true</value> when the user fulfills the policy;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
IEnumerable<IAuthorizationRequirement> requirements);
/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">
/// An optional resource the policy should be checked with.
/// If a resource is not required for policy evaluation you may pass null as the value
/// </param>
/// <param name="policyName">The name of the policy to check against a specific
/// context.</param>
/// <returns>
/// A flag indicating whether authorization has succeeded.
/// Returns a flag indicating whether the user, and optional resource has fulfilled
/// the policy.
/// <value>true</value> when the policy has been fulfilled;
/// otherwise <value>false</value>.
/// </returns>
/// <remarks>
/// Resource is an optional parameter and may be null. Please ensure that you check
/// it is not null before acting upon it.
/// </remarks>
Task<AuthorizationResult> AuthorizeAsync(
ClaimsPrincipal user, object resource, string policyName);
}
El código anterior resalta los dos métodos del IAuthorizationService.
es una interfaz de marcador sin métodos y un mecanismo para rastrear si la autorización es exitosa.
Cada es responsable de comprobar si se cumplen los requisitos:
/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization information.</param>
Task HandleAsync(AuthorizationHandlerContext context);
}
La clase es la que usa el controlador para marcar si se han cumplido los requisitos:
context.Succeed(requirement)
En el código siguiente se muestra la implementación predeterminada simplificada (y anotada con comentarios) del servicio de autorización:
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
// Create a tracking context from the authorization inputs.
var authContext = _contextFactory.CreateContext(requirements, user, resource);
// By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
var handlers = await _handlers.GetHandlersAsync(authContext);
// Invoke all handlers.
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
}
// Check the context, by default success is when all requirements have been met.
return _evaluator.Evaluate(authContext);
}
El código siguiente muestra un típico:
public void ConfigureServices(IServiceCollection services)
{
// Add all of your handlers to DI.
services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...
services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
// Configure your policies
services.AddAuthorization(options =>
options.AddPolicy("Something",
policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
services.AddControllersWithViews();
services.AddRazorPages();
}
Use o para la autorización.
Aplicar directivas al controlador MVC
Si usa Pages, consulte Aplicar directivas a Pages en este documento.
Las directivas se aplican a los controladores mediante el atributo con el nombre de directiva. Por ejemplo:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Index() => View();
}
Aplicar directivas a Pages
Las directivas se aplican a Pages usando el atributo con el nombre de la directiva. Por ejemplo:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}
Las directivas no pueden aplicarse a nivel de controlador de Página de , deben aplicarse a la página.
Las directivas se pueden aplicar a Pages mediante una convención de autorización.
Requirements
Un requisito de autorización es una colección de parámetros de datos que una directiva puede usar para evaluar la entidad de seguridad de usuario actual. En nuestra directiva "AtLeast21", el requisito es un único parámetro: la edad mínima. Un requisito implementa , que es una interfaz de marcador vacía. Un requisito de edad mínima parametrizada podría implementarse de la siguiente manera:
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
Si una directiva de autorización contiene varios requisitos de autorización, todos los requisitos deben pasar para que la evaluación de la directiva se realice correctamente. En otras palabras, varios requisitos de autorización agregados a una sola directiva de autorización se consideran en base a una lógica AND.
Note
Un requisito no necesita tener datos ni propiedades.
Controladores de autorización
Un controlador de autorización es responsable de la evaluación de las propiedades de un requisito. El controlador de autorización evalúa los requisitos en relación con un AuthorizationHandlerContext proporcionado para determinar si se permite el acceso.
Un requisito puede tener varios controladores. Un controlador puede heredar , donde es el requisito que se va a controlar. Como alternativa, un controlador puede implementar para controlar más de un tipo de requisito.
Uso de un controlador para un requisito
En el ejemplo siguiente se muestra una relación uno a uno en la que un controlador de edad mínima usa un único requisito:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com"))
{
//TODO: 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 &&
c.Issuer == "http://contoso.com").Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
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 código anterior determina si la entidad de seguridad de usuario actual tiene una fecha de nacimiento emitida por un emisor conocido y de confianza. La autorización no se puede producir cuando falta la notificación, en cuyo caso se devuelve una tarea completada. Cuando hay una reclamación, se calcula la edad del usuario. Si el usuario cumple la edad mínima definida por el requisito, la autorización se considera correcta. Cuando la autorización se realiza correctamente, se invoca con el requisito satisfecho como único parámetro.
Uso de un controlador para varios requisitos
En el ejemplo siguiente se muestra una relación uno a varios en la que un controlador de permisos puede controlar tres tipos diferentes de requisitos:
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
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);
}
}
}
//TODO: 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;
}
}
El código anterior atraviesa : una propiedad que contiene requisitos no marcados como exitosos. Para un requisito de ReadPermission, el usuario debe ser propietario o patrocinador para acceder al recurso solicitado. Para un requisito de EditPermission o DeletePermission, el usuario debe ser propietario para acceder al recurso solicitado.
Registro del controlador
Los controladores se registran en la colección de servicios durante la configuración. Por ejemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}
El código anterior registra como singleton invocando . Los controladores se pueden registrar mediante cualquiera de las duraciones de servicio integradas.
Es posible agrupar un requisito y un controlador en una sola clase que implemente y . Esta agrupación crea un acoplamiento estricto entre el controlador y el requisito y solo se recomienda para los requisitos y controladores simples. La creación de una clase que implemente ambas interfaces elimina la necesidad de registrar el controlador en DI debido al incorporado que permite que los requisitos se controlen a sí mismos.
Vea la clase para un buen ejemplo donde el es tanto un requisito como el controlador en una clase totalmente autónoma.
¿Qué debe devolver un controlador?
Observe que el método del ejemplo del controlador no devuelve ningún valor. ¿Cómo se indica un estado de éxito o error?
Un controlador indica el éxito llamando a , pasándose el requisito que ha sido validado con éxito.
Por lo general, un controlador no necesita controlar los errores, ya que otros controladores para el mismo requisito pueden tener éxito.
Para garantizar el fracaso, incluso si otros gestores de requisitos tienen éxito, llame a .
Si un controlador llama a o , se sigue llamando a todos los demás controladores. Esto permite que los requisitos produzcan efectos secundarios, como el registro, que tiene lugar incluso si otro controlador ha validado correctamente o ha producido un error en un requisito. Cuando se establece en , la propiedad interrumpe la ejecución de controladores cuando se llama a . El valor predeterminado de es , en cuyo caso se llama a todos los controladores.
Note
Se llama a los controladores de autorización incluso si se produce un error en la autenticación.
¿Por qué necesitaría varios manejadores para un requisito?
En los casos en los que quiera que la evaluación sea en función de OR, implemente varios controladores para un único requisito. Por ejemplo, Microsoft tiene puertas que solo se abren con tarjetas llave. Si dejas tu tarjeta de llave en casa, el recepcionista imprime una pegatina temporal y abre la puerta para ti. En este escenario, tendría un único requisito, BuildingEntry, pero varios controladores, cada uno examinando un único requisito.
BuildingEntryRequirement.cs
using Microsoft.AspNetCore.Authorization;
public class BuildingEntryRequirement : IAuthorizationRequirement
{
}
BadgeEntryHandler.cs
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "BadgeId" &&
c.Issuer == "http://microsoftsecurity"))
{
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;
}
}
TemporaryStickerHandler.cs
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
c.Issuer == "https://microsoftsecurity"))
{
// We'd also check the expiration date on the sticker.
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;
}
}
Asegúrese de que ambos controladores están registrados. Si cualquiera de los controladores tiene éxito cuando una directiva evalúa , la evaluación de la directiva tiene éxito.
Usar una función para cumplir una directiva
Puede haber situaciones en las que el cumplimiento de una directiva es fácil de expresar en el código. Es posible proporcionar un al configurar la directiva con el generador de directivas .
Por ejemplo, el anterior podría reescribirse de la siguiente manera:
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
(c.Type == "BadgeId" ||
c.Type == "TemporaryBadgeId") &&
c.Issuer == "https://microsoftsecurity")));
});
Acceder al contexto de solicitud de MVC en manejadores
El método que implementa en un controlador de autorización tiene dos parámetros: un y el que está controlando. Frameworks como MVC o son libres de agregar cualquier objeto a la propiedad del para transmitir información adicional.
Al usar el enrutamiento de puntos de conexión, el middleware de autorización suele controlar la autorización. En este caso, la propiedad es una instancia de . El contexto se puede usar para acceder al punto de conexión actual, y puede utilizarse para sondear el recurso subyacente al que esté enrutando. Por ejemplo:
if (context.Resource is HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
...
}
Con el enrutamiento tradicional, o cuando la autorización se produce como parte del filtro de autorización de MVC, el valor de es una instancia de . Esta propiedad proporciona acceso a HttpContext, RouteData y todo lo demás proporcionado por MVC y Razor Páginas.
El uso de la propiedad es específico del marco. El uso de información en la propiedad limita las directivas de autorización a marcos concretos. Convierta la propiedad usando la palabra clave , y después confirme que la conversión se ha realizado con éxito para asegurarse de que su código no falle con un cuando se ejecute en otros frameworks.
// Requires the following import:
// using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
}
Requerir globalmente que todos los usuarios se autentiquen
Para obtener información sobre cómo requerir autenticación para todos los usuarios de la aplicación, consulte Crear una aplicación de ASP.NET Core con datos de usuario protegidos por autorización.