Cómo crear un atributo de autorización personalizado en ASP.NET ¿Núcleo?


Estoy tratando de hacer 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 atributo de autorización personalizado?

Lo que estoy tratando de lograr: Estoy recibiendo un ID de sesión en la Autorización del Encabezado. A partir de esa identificación sabré si una acción en particular es válida.

Author: Derek Greer, 2015-07-16

6 answers

El enfoque recomendado por el ASP.Net El equipo central debe utilizar el nuevo diseño de políticas que está plenamente documentado aquí . La idea básica detrás del nuevo enfoque es usar el nuevo atributo [Authorize] para designar una "política" (por ejemplo, [Authorize( Policy = "YouNeedToBe18ToDoThis")] donde la política se registra en el inicio de la aplicación.cs para ejecutar algún bloque de código (es decir, asegurarse de que el usuario tiene una reclamación de edad donde la edad es de 18 años o más).

El diseño de la política es una gran adición al marco y la ASP.Net El equipo Básico de seguridad debe ser elogiado por su introducción. Dicho esto, no es adecuado para todos los casos. La deficiencia 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 determinada requiere un tipo de reclamación determinado. En el caso de que una aplicación pueda tener cientos de permisos discretos que gobiernan las operaciones CRUD en recursos REST individuales ("CanCreateOrder", "CanReadOrder", " CanUpdateOrder", "CanDeleteOrder", etc.), el nuevo enfoque requiere asignaciones uno a uno repetitivas entre un nombre de directiva y un nombre de notificació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, leer todos los tipos de notificación de una base de datos y realizar la llamada mencionada en un bucle). El problema con este enfoque para la mayoría de los casos es que es una sobrecarga innecesaria.

Mientras que el ASP.Net El equipo de seguridad principal 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 utiliza el IAuthorizationFilter para proporcionar una manera simple de expresar un requisito de reclamación para un controlador o acción dada:

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();
    }
}
 203
Author: Derek Greer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-09 23:35:03

Soy el asp.net persona de seguridad. En primer lugar, permítanme disculparme porque nada de esto está documentado aún fuera de la muestra de musicstore o las pruebas unitarias, y todo está siendo refinado en términos de API expuestas.La documentación detallada está aquí.

No queremos que escribas atributos de autorización personalizados. Si necesitas hacer eso, hemos hecho algo malo. En su lugar, debería escribir authorization requirements.

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

Usted dice en los comentarios que desea comprobar un ID de sesión en un encabezado. Su ID de sesión sería la base para una identidad. Si desea utilizar el atributo Authorize escribiría un middleware de autenticación para tomar ese encabezado y convertirlo en un ClaimsPrincipal autenticado. A continuación, debe comprobar que 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 reclamación de fecha de nacimiento en la identidad actual y autorizará si el usuario es mayor de 18;

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, 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();
            }
        }
    }
}

Entonces en tu función ConfigureServices() lo cablearías

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")]
 200
Author: blowdart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-25 16:10:52

Parece que con ASP.NET Core 2, puede heredar de nuevo AuthorizeAttribute, solo necesita implementar también 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<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}
 25
Author: gius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-22 21:42:34

¿Cuál es el enfoque actual para hacer un atributo personalizado AuthorizeAttribute

Fácil: no cree su propio AuthorizeAttribute.

Para escenarios de autorización pura (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<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

Para la autenticación, es mejor manejarlo en el middleware nivel.

¿Qué estás tratando de lograr exactamente?

 18
Author: Pinpoint,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-31 14:06:13

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

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        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<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

Entonces puede usarlo para cualquier atributo personalizado que necesite en sus controladores o acciones. Por ejemplo, para agregar requisitos de permiso. Simplemente 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 cree un Requisito para agregar a su Política

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

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

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

Y finalmente, en tu Startup.método cs ConfigureServices, agregue su controlador de autorización personalizado a los servicios y agregue su política.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Ahora puedes simplemente decorar tus Controladores y Acciones con tu atributo personalizado.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}
 15
Author: Shawn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-29 02:58:01

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<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}
 11
Author: bruno.almeida,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-04 16:52:32