9.5.1 Seguridad basada en claims

Puedes crear tus restricciones personalizadas, agregando claims los cuales son atributos adicionales para por ejemplo validar que:

  1. Solamente los vendedores que tengan categorías pueden ver la lista de productos.

  2. Si tienes vendedores con permisos de solo lectura y vendedores con permisos de lectura y escritura.

  3. Que tus servicios los vean solo personas con una edad de entre 18 y 21 años.

Puedes agregar claims a tu token y en cada servicio validar que según el claim se cumpla con la condición.

Seguridad basada en claims

Para el caso 1, en el login puedes agregar un claim con la clave Categorias y el valor que cuente el número total de categorías. Solamente los usuarios con el rol vendedor que tengan categorías asignadas son los que tendrían este rol.

En tus servicios para validar que solo los empleados con categorías pueden acceder al servicio.

En nuestro archivo Startup en el método ConfigureServices agregamos una nueva regla, le damos un nombre e indicamos que el servicio necesitará tener registrado un claim llamado Categorías (RequireClaim)

Startup.cs
public class Startup
{
   public void ConfigureServices(IServiceCollection services)
   {
       services.AddAuthorization(options =>
      {
           options.AddPolicy("VendedorConCategorias", 
                            policy => policy.RequireClaim("Categorías"));
      }); 
   }
}

En nuestra carpeta Models agregamos nuestro Archivo UsuarioCategoria

UsuarioCategoria.cs
public class UsuarioCategoria
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required(ErrorMessage = "Required")]
    public int UsuarioId { get; set; }

    [Required(ErrorMessage = "Required")]
    public int CategoriaId { get; set; }
}

Agregamos las llaves foráneas e índices en nuestra carpeta EntityConfigurations

UsuarioCategoriaConfiguration.cs
public class UsuarioCategoriaConfiguration
                             : IEntityTypeConfiguration<UsuarioCategoria>
{
    public void Configure(EntityTypeBuilder<UsuarioCategoria> builder)
    {
        builder.HasIndex(u => new { u.UsuarioId, u.CategoriaId })
            .HasName("UI_UsuarioCategoria")
            .IsUnique();

        builder.HasOne(typeof(Usuario))
                .WithMany()
                .OnDelete(DeleteBehavior.Restrict);

        builder.HasOne(typeof(Categoria))
                .WithMany()
                .OnDelete(DeleteBehavior.Restrict);
    }
}

Agregamos el modelo y las llaves foráneas a nuestro archivo CaducaContext

CaducaContext.cs
public class CaducaContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //... Código
        modelBuilder.ApplyConfiguration
                                 (new UsuarioCategoriaConfiguration());
         //... Código
         public virtual DbSet<UsuarioCategoria> 
                                  UsuarioCategoria { get; set; }
    }
}

Actualizamos nuestra base de datos

Update-Database

Cambiamos nuestro método LoginAsync para regresar el total de categorías a las que tiene acceso cada usuario.

En el código donde agregamos como claims los roles, verificamos si el rol es "Vendedor", revisamos cuántas categorías tiene asignadas si tiene al menos una categoría agregamos el claim.

UsuarioDAO.cs
public async Task<TokenDTO> LoginAsync(LoginDTO loginDTO,
                                               IConfiguration config)
{
    //Código
    foreach (var rol in roles)
    {
        claims.Add(new Claim(ClaimTypes.Role, rol));
        if (rol == "Vendedor")
        {
            var totalCategorias = contexto.UsuarioCategoria
                                   .Where(u => u.UsuarioId == usuario.Id)
                                   .Count();
            if (totalCategorias > 0)
                claims.Add(new Claim("Categorias", 
                                           totalCategorias.ToString()));
        }
    }
    //Código       
}

Agregamos a nuestros servicios de Productos que también el usuario Vendedor puede acceder a los servicios.

Agregamos también que el método para ObtenerProductos, debe cumplir con la política de que solo los vendedores con Categorías pueden acceder al servicio

ProductosController.cs
[Authorize(Roles = "Administrador,Vendedor")]
public class ProductosController : ControllerBase
{
    [Authorize(Policy = "VendedorConCategorias")]
    [HttpGet]
    public IEnumerable<Producto> GetProducto()
    {
        return productoDAO.ObtenerTodo();
    }
}

Una desventaja de este tipo de política es que si un usuario inicia sesión y no tenía categorías asignadas y después se le asignan, el usuario debe volver a iniciar sesión

Para el ejemplo 2 de que se tengan usuarios con permisos de lectura, y permisos de lectura y escritura es similar al de las categorías. Puedes realizar el ejemplo para practicar, los pasos son:

  1. Crea una tabla Permisos con los campos: Id(int), Clave (int), Nombre(varchar(100))

  2. Guardar los permisos 1 Lectura, 2 Lectura y Escritura en el archivo CaducaContext

  3. Crea una tabla UsuariosPermisos con los campos UsuarioId (int), PermisoId(int) donde registras los permisos que tiene cada usuario

  4. Crea las llaves foráneas e índices

  5. Agrega las clase del modelo y llaves foráneas a tu archivo CaducaContext

  6. Genera la migración y actualiza la base de datos

  7. Crea una clase UsuariosPermisosDAO con un método obtenerPermisosUsuario, donde regreses los permiso del usuario

  8. En el método LoginAsync agrega como claims los permisos del usuario

Para el ejemplo 3,c puedes agregar un campo de fecha de nacimiento a los usuarios y en método login regresar la edad como claim

  1. Agregar el campo fecha de nacimiento de tipo Date a la tabla usuarios

  2. Agregar la migración

  3. Actualizar la base de datos

  4. Agregar la edad en el login en un claim llamado Edad

  5. Crear una nueva política

services.AddAuthorization(options =>
    {
        options.AddPolicy("Edad1822", policy =>
              policy.RequireClaim("Edad", "18", "19", "20","21", "22"));
    });

6. Agregar la policy al método

[Authorize(Policy = "Edad1822")]
public class UniversitarioController : Controller
{
}

Puedes ver la documentación oficial aquí

Last updated