¿Cómo se crea un AuthorizeAttribute personalizado en ASP.NET Core?

Estoy tratando de crear un atributo de autorización personalizado en ASP.NET Core. En versiones anteriores, era posible anular bool AuthorizeCore(HttpContextBase httpContext) . Pero esto ya no existe en AuthorizeAttribute .

¿Cuál es el enfoque actual para hacer un CustomizeAttribute personalizado?

Lo que bash lograr: recibo una identificación de sesión en la Autorización de encabezado. De esa ID, sabré si una acción en particular es válida.

El enfoque recomendado por el equipo ASP.Net Core es utilizar el nuevo diseño de políticas que está completamente documentado aquí . La idea básica detrás del nuevo enfoque es usar el nuevo atributo [Autorizar] para designar una “política” (por ejemplo, [Authorize( Policy = "YouNeedToBe18ToDoThis")] donde la política se registra en Startup.cs de la aplicación para ejecutar algún bloque de código (es decir, asegúrese de que el usuario tenga un reclamo por edad cuando tenga 18 años o más).

El diseño de la política es una gran adición al marco y el equipo de ASP.Net Security Core debe ser elogiado por su presentación. Dicho esto, no es adecuado para todos los casos. El inconveniente de este enfoque es que no proporciona una solución conveniente para la necesidad más común de simplemente afirmar que un controlador o acción determinado requiere un tipo de reclamo determinado. En el caso donde una aplicación puede tener cientos de permisos discretos que rigen las operaciones CRUD en recursos REST individuales (“CanCreateOrder”, “CanReadOrder”, “CanUpdateOrder”, “CanDeleteOrder”, etc.), el nuevo enfoque requiere repetitivo one-to- una asignaciones entre un nombre de política y un nombre de reclamación (por ejemplo, options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder)); ), o escribir algún código para realizar estos registros en tiempo de ejecución ( por ejemplo, lea todos los tipos de reclamaciones desde una base de datos y realice la llamada mencionada anteriormente en un bucle). El problema con este enfoque para la mayoría de los casos es que es una sobrecarga innecesaria.

Si bien el equipo de ASP.Net Core Security recomienda nunca crear su propia solución, en algunos casos esta puede ser la opción más prudente para comenzar.

La siguiente es una implementación que usa IAuthorizationFilter para proporcionar una forma simple de express un requisito de reclamo para un controlador o acción dados:

 public class ClaimRequirementAttribute : TypeFilterAttribute { public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter)) { Arguments = new object[] {new Claim(claimType, claimValue) }; } } public class ClaimRequirementFilter : IAuthorizationFilter { readonly Claim _claim; public ClaimRequirementFilter(Claim claim) { _claim = claim; } public void OnAuthorization(AuthorizationFilterContext context) { var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value); if (!hasClaim) { context.Result = new ForbidResult(); } } } [Route("api/resource")] public class MyController : Controller { [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] [HttpGet] public IActionResult GetResource() { return Ok(); } } 

Soy la persona de seguridad de asp.net. En primer lugar, permítanme pedirles disculpas por el hecho de que nada de esto está documentado fuera de la muestra de musicstore ni de las pruebas unitarias, y todavía se está refinando en términos de API expuestas. La documentación detallada está aquí .

No deseamos que escriba atributos de autorización personalizados. Si necesitas hacer eso, hemos hecho algo mal. En su lugar, debe escribir los requisitos de autorización.

La autorización actúa sobre las identidades. Las identidades se crean mediante autenticación.

Usted dice en los comentarios que desea verificar una ID de sesión en un encabezado. Su ID de sesión sería la base de una identidad. Si quisiera usar el atributo Authorize , escribiría un middleware de autenticación para tomar ese encabezado y convertirlo en ClaimsPrincipal autenticado. Luego verificaría eso dentro de un requisito de autorización. Los requisitos de autorización pueden ser tan complicados como desee, por ejemplo, aquí hay uno que toma una statement de fecha de nacimiento sobre la identidad actual y autorizará si el usuario es mayor de 18 años;

 public class Over18Requirement : AuthorizationHandler, IAuthorizationRequirement { public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth)) { context.Fail(); return; } var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value); int age = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Today.AddYears(-age)) { age--; } if (age >= 18) { context.Succeed(requirement); } else { context.Fail(); } } } } 

Luego, en su función ConfigureServices() lo conectaría

 services.AddAuthorization(options => { options.AddPolicy("Over18", policy => policy.Requirements.Add(new Authorization.Over18Requirement())); }); 

Y finalmente aplicarlo a un controlador o método de acción con

 [Authorize(Policy = "Over18")] 

Parece que con ASP.NET Core 2, puede volver a heredar AuthorizeAttribute , solo necesita implementar IAuthorizationFilter :

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter { private readonly string _someFilterParameter; public CustomAuthorizeAttribute(string someFilterParameter) { _someFilterParameter = someFilterParameter; } public void OnAuthorization(AuthorizationFilterContext context) { var user = context.HttpContext.User; if (!user.Identity.IsAuthenticated) { // it isn't needed to set unauthorized result // as the base class already requires the user to be authenticated // this also makes redirect to a login page work properly // context.Result = new UnauthorizedResult(); return; } // you can also use registered services var someService = context.HttpContext.RequestServices.GetService(); var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter); if (!isAuthorized) { context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden); return; } } } 

¿Cuál es el enfoque actual para hacer un CustomizeAttribute personalizado?

Fácil: no crees tu propio AuthorizeAttribute .

Para escenarios de autorización puros (como restringir el acceso solo a usuarios específicos), el enfoque recomendado es usar el nuevo bloque de autorización: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84 -L92

 public class Startup { public void ConfigureServices(IServiceCollection services) { services.Configure(options => { options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore")); }); } } public class StoreController : Controller { [Authorize(Policy = "ManageStore"), HttpGet] public async Task Manage() { ... } } 

Para la autenticación, se maneja mejor en el nivel de middleware.

¿Qué estás tratando de lograr exactamente?

Puede crear su propio AuthorizationHandler que encontrará atributos personalizados en sus Controladores y Acciones, y pasarlos al método HandleRequirementAsync.

 public abstract class AttributeAuthorizationHandler : AuthorizationHandler where TRequirement : IAuthorizationRequirement where TAttribute : Attribute { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement) { var attributes = new List(); var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor; if (action != null) { attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType)); attributes.AddRange(GetAttributes(action.MethodInfo)); } return HandleRequirementAsync(context, requirement, attributes); } protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable attributes); private static IEnumerable GetAttributes(MemberInfo memberInfo) { return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast(); } } 

Luego puede usarlo para cualquier atributo personalizado que necesite en sus controladores o acciones. Por ejemplo, para agregar requisitos de permiso. Solo crea tu atributo personalizado.

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class PermissionAttribute : AuthorizeAttribute { public string Name { get; } public PermissionAttribute(string name) : base("Permission") { Name = name; } } 

Luego crea un Requisito para agregar a tu Política

 public class PermissionAuthorizationRequirement : IAuthorizationRequirement { //Add any custom requirement properties if you have them } 

A continuación, cree AuthorizationHandler para su atributo personalizado, heredando AttributeAuthorizationHandler que creamos anteriormente. Se le pasará un IEnumerable para todos sus atributos personalizados en el método HandleRequirementsAsync, acumulado desde su Controlador y Acción.

 public class PermissionAuthorizationHandler : AttributeAuthorizationHandler { protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable attributes) { foreach (var permissionAttribute in attributes) { if (!await AuthorizeAsync(context.User, permissionAttribute.Name)) { return; } } context.Succeed(requirement); } private Task AuthorizeAsync(ClaimsPrincipal user, string permission) { //Implement your custom user permission logic here } } 

Y finalmente, en su método Startup.cs ConfigureServices, agregue su AuthorizationHandler personalizado a los servicios y agregue su Política.

  services.AddSingleton(); services.AddAuthorization(options => { options.AddPolicy("Permission", policyBuilder => { policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement()); }); }); 

Ahora puede simplemente decorar sus Controladores y Acciones con su atributo personalizado.

 [Permission("AccessCustomers")] public class CustomersController { [Permission("AddCustomer")] IActionResult AddCustomer([FromBody] Customer customer) { //Add customer } } 

Basado en la GRAN respuesta de Derek Greer, lo hice con enumeraciones.

Aquí hay un ejemplo de mi código:

 public enum PermissionItem { User, Product, Contact, Review, Client } public enum PermissionAction { Read, Create, } public class AuthorizeAttribute : TypeFilterAttribute { public AuthorizeAttribute(PermissionItem item, PermissionAction action) : base(typeof(AuthorizeActionFilter)) { Arguments = new object[] { item, action }; } } public class AuthorizeActionFilter : IAsyncActionFilter { private readonly PermissionItem _item; private readonly PermissionAction _action; public AuthorizeActionFilter(PermissionItem item, PermissionAction action) { _item = item; _action = action; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :) if (!isAuthorized) { context.Result = new UnauthorizedResult(); } else { await next(); } } } public class UserController : BaseController { private readonly DbContext _context; public UserController( DbContext context) : base() { _logger = logger; } [Authorize(PermissionItem.User, PermissionAction.Read)] public async Task Index() { return View(await _context.User.ToListAsync()); } }