Cómo Manejar el TrustStore de Java sin Volverse Loco

KeyStore Explorer 5.1

KeyStore Explorer 5.1

Para trabajar con certificados SSL, key stores y trust stores las herramientas de línea de comando son bastante áridas. Por suerte existe KeyStore Explorer. Me animaría a decir que tiene todo lo necesario, pero lo más importante es que es muy fácil de usar, tiene ayuda integrada y muchas opciones.

Muy poca gente menciona esta herramienta cuando se trata de orientar a la gente que tiene que hacer alguna operación con claves o certificados SSL, pero es muy recomendable.

Principalmente convierte en transparente un proceso que suele ser bastante opaco si no se sabe bien lo que se está haciendo con la herramienta keytool.

Hashes y Ataques de Denial of Service: Java, Python, Ruby, PHP y ASP

En casi todos los lenguajes de programación hay algún tipo de estructura de datos como una tabla hash (llamados por ejemplo hash map, hash table, dictionary, entre otros). Básicamente permiten asociar un valor a una clave y posteriormente obtener dicho valor proporcionando la clave en un tiempo constante sin importar cuantas claves haya en la estructura.

Para lograr que la búsqueda sea en tiempo constante, los valores se almacenan en una posición de la tabla según el valor de hash de su clave.

Es posible, aunque poco probable, que dos claves distintas generen una colisión, es decir que dos claves distintas generen el mismo valor de hash y por lo tanto dos valores deban guardarse en una misma posición. Una forma de hacer eso es tener una lista de valores en lugar de solo un valor en cada posición. Luego de identificar la posición mediante el valor de hash se hace una búsqueda secuencial entre todos los elementos de esa lista.

Un ataque de denegación de servicio mediante hash o Hash DoS consiste en explotar esas colisiones para hacer que la lista de valores sea muy larga y se requiera mayor tiempo de procesamiento para buscar, agregar y eliminar elementos.

En lenguajes usados para aplicaciones Web como PHP, Python, Java, Ruby o ASP, los valores ingresados mediante un formulario web son cargados en tablas hash para su posterior procesamiento y por eso todos resultaron vulnerables a esos ataques.

En esta transparencia de PHP se muestra un requerimiento POST creado para explotar esta vulnerabilidad: http://talks.php.net/show/phpuk2012/14

Existen tres formas de atacar el problema, con diferentes grados de éxito y complicación.

  • PHP, ColdFusion y ASP optaron por “controlar los daños” limitando a 1.000 la cantidad de valores de entrada que pueden llegar en un requerimiento POST. En el php.ini de PHP 5.3.9 en adelante hay una configuración max_input_vars=1000.
  • Python [http://bugs.python.org/issue13703], Ruby y Java 7 adoptaron una mejora en el algoritmo de hash para dificultar la generación de colisiones incorporando números pseudoaleatorios en su cálculo.
  • Java 8 incorpora un árbol balanceado para almacenar todos los valores cuyas claves tengan el mismo hash [http://openjdk.java.net/jeps/180]. De esa forma con miles de millones de valores, apenas requeriría 50 operaciones para resolver la búsqueda.

Es importante destacar que la solución adoptada por PHP, ASP y ColdFusion de limitar la longitud de la entrada no resuelve por completo el problema ya que por ejemplo si se envía un hash con muchas colisiones dentro de un string JSON, pueden ser menos de 1.000 parámetros e igual disparar el problema. Básicamente las aplicaciones deben igualmente verificar internamente los datos que reciben.

La alternativa de mejorar la función de hash es un bastante más robusta, aunque puede ser más difícil de implementar. En algunos lenguajes podría tener un impacto negativo en la performance o requerir cambios en algún comportamiento ya definido.

La solución elegida en Java 8 (y que vuelve atrás los cambios incorporados en Java 7 para tratar este mismo problema) es muy robusta y elimina la causa fundamental del problema.

Java Para Cálculos Numéricos

A veces se utiliza Java para hacer cálculos numéricos intensivos a pesar de su fama de ser lento. Muchas veces la rapidez de cálculo es un aspecto importante, pero casi nunca el único a la hora de elegir el lenguaje para implementar estos cálculos. Java tiene muchas cosas atractivas para este tipo de aplicaciones como el hecho de generar programas portables, de tener muy buen soporte para programación paralela y tener «garbage collector» entre otros. Pero, ¿qué tan lento o rápido es realmente para este tipo de cálculos?

Para tener una idea decidí comparar Java con C que es el lenguaje rápido por excelencia aunque en este campo muchos consideran a Fortran como en rey de la computación numérica.

Para la comparación tomé el algoritmo de Chudnovsky, que calcula el número PI con la cantidad de dígitos que se quiera, mientras se disponga de memoria suficiente para almacenarlos.

Para el cálculo en C usé la biblioteca GMP que permite hacer cálculos con precisión arbitraria y se autoproclama como la implementación más rápida del cálculo de PI.

En Java usé la biblioteca Apfloat que también ofrece la posibilidad de hacer cálculos con precisión arbitraria y que promete ser más rápida que la implementación de BigDecimal de Java para números grandes.

En esta era de computadoras con múltiples núcleos, opté por realizar el cálculo utilizando todo el paralelismo disponible y para ello utilicé dos implementaciones paralelizadas del algoritmo en cuestión. Apfloat ya trae una implementación paralela del algoritmo de Chudnovsky como ejemplo de uso y la implementación de C está basada en OpenMP.

  • Algoritmo de Chudnovsky en C
  • Algoritmo de Chudnovsky en Java
  • Opciones de compilación de C: GCC: 4.7.2  -fopenmp -Wall -O2 -lgmp -lm
  • Versión de GMP 5.0.2
  • Versión de Apfloat 1.7.1
  • Versión Java: OpenJDK 1.7.0u15
  • Hardware: AMD Phenom X4 9550 4 GB RAM
  • Sistema operativo: Ubuntu 12.10 64 bits
  • Memoria disponible para Java 2 GB

Parámetros usados para Apfloat

  • builderFactory=org.apfloat.internal.LongBuilderFactory
  • defaultRadix=10
  • cacheL1Size=131072
  • cacheL2Size=524288
  • cacheBurst=64
  • memoryTreshold=402653184
  • sharedMemoryTreshold=268435456
  • blockSize=1048576
  • numberOfProcessors=4
  • filePath=
  • fileInitialValue=0
  • fileSuffix=.ap
  • cleanupAtExit=true

Resultados

Dígitos Apfloat (segundos)
GMP (segundos) Apfloat/GMP
100.000 0,435 0,041 10,61
1.000.000 3,987 0,595 6,70
10.000.000 67,821 8,044 8,43
100.000.000 745,732 111,824 6,67

Los resultados muestran que en esta prueba la versión Java llega a ser 6 veces más lenta que GMP.

Como Apfloat utiliza almacenamiento en disco cuando los números son más grandes que un cierto tamaño (configurable), elegir la configuración adecuada lleva un poco de pruebas. La configuración por omisión no está del todo optimizada para computadoras actuales con varios gigas de RAM.

Código de la Prueba

El código C es el citado más arriba.

Java

package calculatepi;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatRuntimeException;
import org.apfloat.samples.Operation;
import org.apfloat.samples.Pi;
import org.apfloat.samples.PiParallel;

public class App {

    public static void main(String[] args) {
        final int max = Integer.parseInt(args[0]);
        Writer nullWritter = new Writer() {
            @Override
            public void write(char[] chars, int i, int i1) throws IOException {
            }

            @Override
            public void flush() throws IOException {
            }

            @Override
            public void close() throws IOException {
            }
        };

        try (
                PrintWriter out = new PrintWriter(nullWritter);
                PrintWriter err = new PrintWriter(new File("/home/user/apfloatPierr.txt"))) {
            Pi.setOut(out);
            Pi.setErr(err);
            final Operation warmUpOp = new PiParallel.ParallelChudnovskyPiCalculator(1000000, 10);

            final List<Operation> ops = new ArrayList<>();

            for (int i = 100000; i <= max; i = i * 10) {
                ops.add(new PiParallel.ParallelChudnovskyPiCalculator(i, 10));
            }

            for (int i = 0; i < 5; i++) {
                PiParallel.run(1000000, 10, warmUpOp);
            }
            int digits = 100000;
            for (Operation op : ops) {
                err.println("====================================");
                PiParallel.run(digits, 10, op);
                digits = digits * 10;
            }

        } catch (IOException | ApfloatRuntimeException ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

Bash

#!/bin/sh

HEAP=2g
OUTFILE=apfloatPierr.txt
rm $OUTFILE

echo "Java..."

java -Xmx$HEAP -Xms$HEAP -jar /home/user/CalculatePi.jar 100000000
sleep 1

echo "C..."
echo "100000" >> $OUTFILE
./pi 100000 0 4 1> /dev/null 2>> $OUTFILE
echo "1000000" >> $OUTFILE
./pi 1000000 0 4 1> /dev/null 2>> $OUTFILE
echo "10000000" >> $OUTFILE
./pi 10000000 0 4 1> /dev/null 2>> $OUTFILE
echo "100000000" >> $OUTFILE
./pi 100000000 0 4 1> /dev/null 2>> $OUTFILE

Entorno de Desarrollo de C/C++ para Windows

Instalación de Cygwin

Cygwin es una colección de herramientas de GNU para Windows. Entre las herramientas de desarrollo nos interesa el compilador GCC, el debugger GBD y demás herramientas necesarias para compilar como MAKE.

Ir a http://www.cygwin.com/ y bajar el instalador de Cygwin (http://www.cygwin.com/setup.exe).
cygwin-1

Ejecutar setup.exe y seguir la instalación.


Si se está en una computadora con conexión a Internet, se debe elegir la opción “Install from Internet”.

En la carpeta llamada Root Directory se van a instalar las herramientas que seleccionemos.

En la carpeta seleccionada quedarán los paquetes que descarguemos para una futura reinstalación o para copiarlos a otra computadora e instalarlos en ella.


Se debe elegir desde dónde descargar los paquetes. Si se elige un servidor muy lento o que no funciona, se puede cambiar por otro.

Muchos paquetes ya están seleccionados, pero además se deben marcar para instalar los siguientes de la categoría devel: automake1.9, cppcheck, gcc4, gdb, make, libmpfr4




Configurar Path

Finalizada la instalación se debe agregar la carpeta donde están las herramientas al Path de Windows. Yendo a Panel de ControlSystem and SecuritySystem y eligiendo Advanced system settings.

En la solapa Advanced, seleccionar el botón Environment Variables….

Se debe crear o editar una variable de usuario seleccionado en botón New… o Edit… debajo del recuadro User variables for….
Si la variable Path ya existe, se la edita y si no existe, se la crea nueva.

En caso de crearla, se le pone el nombre Path y como valor el nombre de la carpeta elegida para instalar Cygwin. Si la variable Path ya existía, simplemente al modificar su valor agregar al final un punto y coma y luego el nombre completo de la carpeta: C:\path\anterior;C:\Otro\Path\anterior;C:\cygwin\bin

Instalación de un Editor

Para escribir nuestros programas sirve cualquier editor que esté hecho para programar. Típicamente estos editores resaltan la sintaxis del lenguaje y permiten mostrar números de línea, entre otras cosas. Gedit es uno de ellos. El bloc de notas de Windows no es recomendable.

Está disponible para Windows.

Su instalación es muy sencilla.







Uso de las Herramientas

Cygwin crea un entorno UNIX completo dentro de Windows. Este incluye, además de las herramientas de programación que instalamos, un shell. Para usar el compilador y las herramientas de Cygwin debemos hacerlo mediante ese shell al que accedemos ejecutando Cygwin Terminal. El directorio “activo” inicialmente al abrir Cygwin Terminal está ubicado dentro de la carpeta donde lo instalamos. Podemos situar nuestros programas ahí o en cualquier otra parte y movernos hacia esa otra carpeta usando los comandos del shell. En la imagen se observa un programa guardado en la carpeta “c”.

Para compilar y ejecutar el programa que escribimos, debemos abrir Cygwin Terminal. No es lo mismo abrir el shell de Windows “cmd” ya que Windows no soporta algunas de las características que tiene UNIX como los links simbólicos y es probable que tengamos errores extraños al ejecutar las herramientas de Cygwin.

En este ejemplo para compilar y ejecutar el programa que escribimos y guardamos debemos ejecutar estos comandos:

  • cd c
  • gcc -o helloworld -Wall -std=c99 helloworld.c
  • ./helloworld

Instalación sin acceso a Internet

Para instalar en una computadora sin conexión a Internet se debe primero ejecutar el instalador en otra donde sí se disponga de conexión y utilizar la opción
Download Without Installing. Luego proseguir con la instalación. El instalador va a descargar los paquetes seleccionados en una carpeta local. Esa carpeta puede ser copiada a otra computadora que no tenga conexión a Internet.

En la computadora sin conexión a Internet se debe ejecutar el instalador y seleccionar la opción Install From Local Directory.

Luego se debe indicar en qué carpeta se copiaron los paquetes descargados anteriormente y proseguir con la instalación.

Aritmética Binaria y Decimal

Por Qué Nunca Usar float y double para Representar Montos de Dinero

Cuando aprendemos a programar una de las primeras lecciones es que se usan diferentes tipos de datos para representar números enteros y números no enteros. Una variable entera no puede tener el valor 0,5. En Pascal se usan los tipos Integer y Real y en C están por ejemplo los tipos int, long, float y double.

Supongamos que tenemos que hacer un sistema de permita administrar sumas de dinero; más específicamente, deudas y pagos. Puede ser para un banco, una tarjeta de crédito, un almacén o cualquier aplicación comercial. Para dicho sistema tenemos que hacer una función en C muy sencilla que reciba la deuda actual y un arreglo de pagos que realizó el deudor y que devuelva la deuda actualizada una vez descontados los pagos. Este podría ser el prototipo:

double actualizarDeuda(double deuda, double pagos[], int cantPagos);

Como la deuda puede ser con centavos, no podemos usar int para representarla entonces usamos double.

Lo que hace la función es restar de la deuda cada elemento del arreglo pagos y devolver el monto actualizado.

double actualizarDeuda(double deuda, double pagos[], int cantPagos){
    double deudaActualizada = deuda;
    int i;
    for(i = 0; i<cantPagos;i++){
        deudaActualizada -= pagos[i];
    }
    return deudaActualizada;
}

Vamos a constatar que la función hace lo esperado con una prueba.

Supongamos que un deudor tenía una deuda de $308,21 y realizó 4 pagos por $8,01; $0,20; $299,99 y $0,01. Si bien no es sencillo pagar en efectivo montos como $8,01, es un caso que puede darse perfectamente en transacciones electrónicas.

double deuda = 308.21d;
double pagos[4] = {8.01d, 0.20d, 299.99d, 0.01d};

En teoría la deuda está saldada así que la función debería devolvernos que la deuda es $0,00.

double miDeuda = actualizarDeuda(deuda, pagos, 4);
if(miDeuda == 0.0d){
    printf("Libre deuda.\n");
} else {
    printf("Al Veraz.\n");
}

Sin embargo la prueba imprime Al Veraz. es decir que nuestro sistema sigue registrando una deuda a pesar de que está saldada.

La nueva deuda no es $0,00, pero casi. El valor es $-0,00000000000000909516. Es decir, nuestra función nos calculó que el deudor pagó 9 milbillonésimas de peso más de lo que debía.

Esto nos hace pensar que hay un error de redondeo así que decidimos usar la función round.

miDeuda = round(actualizarDeuda(deuda, pagos, 4));
if(miDeuda == 0.0d){
    printf("Redondeando, logro el libre deuda.\n");
} else {
    printf("Redondeando, igual voy al Veraz.\n");
}

Ahora sí obtenemos el resultado Redondeando, logro el libre deuda lo que es en principio correcto.

A pesar de que en muchos sistemas el problema de la precisión está “solucionado” de esa manera, no se está atacando la causa del problema sino una de sus consecuencias. Como se observa a continuación, redondear el resultado puede llevarnos a dar por saldada una deuda que todavía no está totalmente paga.

pagos[2]-=0.4d;
miDeuda = round(actualizarDeuda(deuda, pagos, 4));
if(miDeuda == 0.0d){
    printf("Con redondeo, debiendo $0,40 igual logro el libre deuda.\n");
} else {
    printf("A pesar de redondear, como debo voy al Veraz.\n");
}

Si descontamos $0,40 de uno de los pagos, está claro que no deberíamos extender el libre deuda. Sin embargo, obtenemos el cartel Con redondeo, debiendo $0,40 igual logro el libre deuda lo que es incorrecto y le hace perder dinero quien nos encargó hacer el sistema. Es una situación complicada para nosotros ya que desde el punto de vista del acreedor, es una atrocidad.

Este problema lo tenemos en cualquier lenguaje de programación y también lo tenemos en muchas aplicaciones muy usadas para hacer cálculos con montos de dinero.

arit-excelLa única solución que ofrecen al problema en el caso de esa planilla de cálculo tan famosa es “redondear” (http://support.microsoft.com/kb/214118/es).

La causa del problema está en la representación binaria de los números de coma flotante. Como no todos los números son representables se tiene que buscar el número más cercano al especificado y eso implica un error en la representación. Por ejemplo el número 0,11 no es representable como double (porque en binario es periódico) y es aproximado con 0,11000000000000000056.

Los números de coma flotante están definidos por el estándar IEEE-754 que data de 1985. A partir de 2008, el estándar incorporó además de los números de binarios de coma flotante que ya existían (que usan base 2 como los float y double) nuevos números decimales de coma flotante (que usan base 10).

Esa es la solución al problema de la precisión que estamos teniendo. Vamos a reescribir la función de esta forma:

_Decimal64 actualizarDeuda(_Decimal64 deuda, _Decimal64 pagos[],int cantPagos){
    _Decimal64 deudaActualizada = deuda;
    int i;
    for(i = 0; i<cantPagos;i++){
        deudaActualizada -= pagos[i];
    }
    return deudaActualizada;
}

Usamos el tipo _Decimal64 en lugar de double. El resto es igual.

Y ahora sin aplicar ningún redondeo, las cuentas simplemente dan el resultado esperado.

_Decimal64 deuda = 308.21dd; 
/* el sufijo dd es el que corresponde al tipo _Decimal64 */
_Decimal64 pagos[4] = {8.01dd, 0.20dd, 299.99dd, 0.01dd};
_Decimal64 miDeuda = actualizarDeuda(deuda, pagos, 4);
if(miDeuda == 0.0dd){
    printf("Con _Decimal64, obtengo en libre deuda normalmente.\n");
} else {
    printf("Con _Decimal64, al Veraz.\n");
}

El soporte de decimales de coma flotante está disponible a partir de GCC 4.2, pero no está disponible para todas las plataformas. En particular en Windows con Cygwin obtenemos un error que dice “error: decimal floating point not supported for this target”.

Otros compiladores sí lo soportan en Windows como por ejemplo el de Intel. http://software.intel.com/en-us/articles/using-decimal-floating-point-with-intel-c-compiler/.

Más información

  1. http://en.wikipedia.org/wiki/IEEE_754-2008
  2. http://speleotrove.com/decimal/

Programar en C con Garbage Collection

De los 20 lenguajes mejor ubicados en el índice TIOBE, 14 tienen alguna forma de “Garbage Collection“. Esos 14 lenguajes representan más de un 47% de peso en el índice. Los únicos lenguajes “importantes” que no tienen recolección automática de basura son C, C++ y Objective-C (Delphi, Pascal y Ada son minoritarios).

El caso de Objective-C es especial ya que desde la versión 2.0 tiene un garbage collector, pero es opcional y no está en la versión que se usa para desarrollar aplicaciones para el iPhone.

En C y C++ se puede usar el garbage collector de Boehm-Demers-Weiser. Básicamente en lugar de llamar a malloc, llamas a GC_MALLOC y los llamados a GC_FREE son opcionales.

En Ubuntu, debemos instalar libgc

 sudo apt-get install libgc-dev libgc1c2

y al compilar debemos usar la opción -lgc para indicarle al linker que use libgc.

gcc -std=c99 -Wall  -o programa *.c -lgc

Conceptos Básicos de Spring Security

Spring Security es el framework de autenticación y control de acceso de Spring.

Autenticación en el caso más usual es determinar que el usuario que accede al sistema es quien dice ser. Spring soporta muchísimas formas de hacer la autenticación y van desde la más básica que es un simple formulario HTML hasta otras como OpenID, CAS, LDAP, Kerberos, JAAS, etc.

Control de acceso es determinar si un usuario puede realizar una determinada acción dentro del sistema. En muchos casos no tiene sentido realizar el control de acceso usuario por usuario sino agrupando a todos los usuarios que pueden realizar las mismas acciones dentro del sistema. Es decir, no se define “María y Roberto pueden imprimir facturas” sino, “todos los usuarios que sean vendedores pueden imprimir facturas”. Si hoy Juan es encargado de limpieza entonces no puede imprimir facturas, pero el día de mañana, Juan podría ser ascendido a área de ventas y ser vendedor, por lo que entonces sí podrá imprimir facturas. La forma de agrupar a “todos los usuarios que sean vendedores” es mediante roles. Entonces en un momento sólo María y Roberto tienen el rol “ROLE_VENDEDOR” y posteriormente se agregará Juan a ese grupo asignándole dicho rol.

Entender que un rol no es otra cosa que un conjunto de usuarios es muy importante.

Spring permite controlar el acceso en tres niveles

  1. URLs de una aplicación WEB,
  2. métodos de un Bean de Spring.
  3. objetos del dominio.

La única diferencia entre las tres es qué operación es la que se está controlando.

En el primer caso, se busca controlar el acceso a un grupo de recursos que son identificables mediante una URL. Por ejemplo si nuestra aplicación web tiene una página que sólo debe ser accedida por un gerente y no por todos los demás empleados, una estrategia sería que la URL de esa página fuera por ejemplo “/gerencia/index.jsp” y se configurara Spring Security para que sólo usuarios con rol “ROLE_GERENTE” pudieran acceder a URLs que empezaran con “/gerencia”.

El segundo caso busca controlar la ejecución de un método en particular. Se usa en situaciones donde no se trata de una aplicación web, cuando no es posible tener URLs distintas para diferenciar el acceso o cuando independientemente de qué URL fue solicitada desde el cliente queremos asegurarnos de que no se ejecute un método si no se trata del usuario que tiene el permiso. Los métodos que pueden ser controlados son métodos de objetos que sean Beans de Spring, no cualquier objeto.

El tercer caso busca controlar el acceso a objetos de dominio. El ejemplo más claro es para extraer dinero de una cuenta bancaria. Se debe verificar que el usuario sea el dueño de esa cuenta en particular y la relación de quién es dueño de qué cuenta puede ir variando en el tiempo por ejemplo a medida que se agregan o quitan titulares. En este caso los objetos que se desean controlar no tienen que ser Beans de Spring, puede ser cualquier objeto.

Para resolver el tercer caso se usa un módulo de Spring Security que se llama Domain Object Security o Spring ACLs.

Con Spring ACLs se define para cada objeto (instancias individuales) del dominio que se necesite controlar una lista de usuarios y roles que pueden accederlo. Como un rol no es otra cosa que un conjunto de usuarios, en verdad lo que se indica para cada objeto del dominio es directa o indirectamente una lista de usuarios.

Gráficamente lo que se tiene es un objeto del dominio, como por ejemplo una cuenta bancaria:

{Cuenta 123541500542}

y asociada al objeto una lista de usuarios que están autorizados a sacar dinero de esa cuenta

{Cuenta 123541500542} -> {“Juan”, “Pedro”, “Susana”}

Cuando la aplicación está por realizar la operación de débito sobre esa cuenta debemos indicarle a SpringSecurity que verifique que el usuario que está realizando la operación esté en la lista de usuarios de la cuenta sobre la que se está operando.

¿Cómo sería esto con la intervención de roles? Supongamos que existiera en el sistema un rol “ROLE_GERENTE_DE_CUENTAS” y que de acuerdo a las reglas del banco un usuario con ese rol está autorizado a realizar débitos sobre cualquier cuenta. La ACL quedaría así

{Cuenta 123541500542} -> {“Juan”, “Pedro”, “Susana”, “ROLE_GERENTE_DE_CUENTAS”}

Spring Security verificará en el momento que le sea indicado si el usuario que está intentando realizar la operación es Juan, Pedro, Susana, o algún usuario que tenga asignado el rol “ROLE_GERENTE_DE_CUENTAS”. Nuevamente,  como un rol no es otra cosa que un conjunto de usuarios en la lista la presencia de un rol es sólo una forma indirecta de referirnos a usuarios.

Si en cambio se define a los roles como un conjunto de “operaciones” o un conjunto de “permisos” es más difícil entender qué hace el rol en la lista de usuarios autorizados y por lo tanto entender cómo adaptar Spring ACLs a nuestra aplicación.

Spring ACLs es muy flexible y potente ya que permite manejar varios permisos como lectura, escritura, borrado y además permite otorgar y revocar permisos. Por ejemplo si le otorgamos permiso al rol “ROLE_GERENTE” y se lo revocamos al usuario “Juan” que  tiene ese rol, lo que logramos es definir que todos los gerentes menos “Juan” tienen otorgado el permiso. También podría ser a la inversa y definiríamos que ningún gerente tenga permiso a excepción de “Juan”. Si vemos los roles  como conjuntos de usuarios, es simplemente quitar elementos del conjunto.

Para aprender más en detalle cómo usar Spring ACLs, se pueden consultar estos dos artículos