1. Cuando los roles no son suficientes
Si miramos los roles solo con los ojos de un principiante, parecen la solución ideal: cortos, comprensibles, casi como insignias en una conferencia. Pero un proyecto real pronto comienza a hacer preguntas incómodas. Por ejemplo: “El editor puede ver la cola de revisión, pero solo el editor senior puede publicar”, o “El administrador puede leer usuarios, pero no cualquier administrador puede cambiar roles”. Y si intentas expresar todo solo con roles, rápidamente empezarás a crear entidades del tipo SUPER_ADMIN_OF_THE_UNIVERSE, que suena divertido exactamente hasta la primera revisión.
En nuestro proyecto, esto se manifiesta de manera muy natural. Tenemos toda una zona /api/editor/** y dentro de ella diferentes acciones: ver la cola, rechazar material, publicar material. Si decimos “todo esto es accesible para el rol EDITOR”, automáticamente otorgaremos los mismos permisos a todos los editores. Y si mañana se necesita dividir los derechos, tendremos que cambiar el modelo de roles (rompiendo decisiones anteriores) o agregar cuidadosamente permisos específicos.
Aquí es donde se vuelve útil un segundo nivel de expresividad: authority como un permiso separado y preciso sobre una acción.
Modelo: rol — zona, authority — acción
Ahora es importante no perderse en términos, por lo que nos mantendremos en una lógica simple. El rol responde a la pregunta: “¿A qué clase de usuarios pertenece una persona?” o “¿A qué zona está admitida?”. Es una etiqueta de grano grueso. Authority responde a la pregunta: “¿Qué acción específica se le permite?”. Es un derecho de grano fino. En un buen sistema, no compiten sino que se complementan: el rol otorga un área de acceso básica, la authority detalla las operaciones concretas dentro de esa área.
Imagina una oficina. El rol es el pase de piso: “empleado”, “editor”, “administrador”. La authority es el derecho a presionar un botón específico: “puede imprimir documentos”, “puede firmar contratos”, “puede apagar el servidor (por favor, no)”. Si intentas crear un rol para cada botón, obtendrás 200 roles y un desafortunado desarrollador que los mantiene. Normalmente, ese desarrollador eres tú del futuro.
Vamos a fijar la diferencia en una tabla compacta:
| Característica | Role (rol) | Authority (derecho) |
|---|---|---|
| Significado | “zona/clase de usuario” | “acción concreta” |
| Granularidad | gruesa | precisa |
| Buenos ejemplos | USER, , |
draft:publish, , |
| Dónde es cómodo leer | “mapa de la aplicación” en SecurityFilterChain | reglas concretas en endpoints individuales |
| Peligro principal | permisos demasiado amplios “por defecto” | caos de cadenas y errores tipográficos |
2. Comprobaciones: hasRole(...) y hasAuthority(...)
Cuando escribimos reglas de acceso en SecurityFilterChain, Spring Security al final compara: “¿el usuario actual tiene el derecho necesario o no?”. Tanto el rol como la authority se verifican mediante el mismo concepto: conjunto de derechos del usuario. La diferencia está en cómo llamamos a esos derechos y cómo los verificamos: “en modo rol” o “en modo cadena exacta”.
A nivel de configuración, esto suele verse así: usamos roles para grandes zonas y authorities para acciones específicas. En el proyecto será especialmente claro en las acciones del editor: “ver la cola” y “publicar” son derechos diferentes, aunque ambos pertenecen a la parte editorial.
Un pequeño fragmento de configuración: aquí nos importa la diferencia entre zona y acción específica, sin profundizar en cómo los roles se ven luego como cadenas authority:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth
// Usamos el rol como "marcador de zona": admitimos editores en el endpoint específico.
.requestMatchers("/api/editor/review-queue").hasRole("EDITOR")
// Usamos authority como acción específica: publicar requiere un derecho explícito.
.requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
// Todas las demás solicitudes en esta configuración se deniegan por defecto.
.anyRequest().denyAll()
).build();
}
}
El significado: para ver la cola, “dejamos pasar a los editores”, mientras que para la acción concreta de publicación exigimos un derecho explícito draft:publish. Esto ya hace que el modelo de acceso sea “más maduro”: el rol sigue siendo un mapa cómodo, la authority añade precisión donde el rol sería demasiado burdo.
Aquí es importante notar de antemano un punto práctico: si tienes un matcher general (por ejemplo, /api/editor/**) y otro más específico (por ejemplo, /api/editor/drafts/*/publish), entonces el específico debe ir arriba. Esto no es nuevo (ya hablamos sobre el orden de las reglas), pero precisamente con las authorities empiezas a sentir especialmente por qué el orden no es un “estilo”, sino lógica.
3. Nombres de authorities: formato resource:action
Ahora sobre la parte “menos visible” pero súper importante: cómo nombrar las authorities. Si los roles suelen limitarse a tres o cinco valores y viven décadas, las authorities tienden a multiplicarse. Y si no estableces un esquema de nombres claro, en una semana obtendrás un conjunto de cadenas como CAN_PUBLISH, PUBLISH_DRAFT, DRAFT_PUBLISH, publishDraft, draftPublishPermission — y todo significará lo mismo, pero se verificará de diferentes maneras. Una excelente forma de complicarte la vida en la depuración.
En nuestro curso fijamos un formato simple y legible: resource:action. Recurso — “qué protegemos”, acción — “qué hacemos”.
Por ejemplo:
- profile:read, profile:write
- avatar:upload
- draft:create, draft:publish
- user:read, user:manage
Un nombre así es agradable porque se puede leer en los registros y entender inmediatamente el significado. Y si ves user:manage, no necesitas adivinar: “¿es sobre lectura? ¿sobre modificación? ¿sobre magia?”. Es sobre gestión de usuarios. Punto.
Para no mantener cadenas a mano en diez lugares, es conveniente sacarlas a constantes. No es un requisito obligatorio hoy mismo, pero como “disciplina de proyecto” ayuda mucho:
package com.example.securecontent.security;
// Constantes para authorities: para no esparcir "cadenas mágicas" por el proyecto.
public final class Authorities {
// Prohibimos la creación de instancias: esta clase es solo un "contenedor" para cadenas.
private Authorities() {}
// Authority para publicar un borrador.
public static final String DRAFT_PUBLISH = "draft:publish";
// Authority para gestionar usuarios (por ejemplo: cambio de roles, bloqueos, etc.).
public static final String USER_MANAGE = "user:manage";
}
El sentido aquí no es solo la “estética”. El sentido es que una cadena es algo frágil. Un espacio de más y obtendrás un sistema de seguridad perfecto que “por alguna razón siempre da 403”.
4. Roles y authorities en el proyecto
En esta etapa del curso no construimos un ACL empresarial, no escribimos un motor de políticas ni simulamos que tenemos un banco. Construimos un proyecto educativo y tenemos un buen principio sensato: el rol es el lenguaje principal de las zonas, la authority es el lenguaje de las acciones específicas. Esto proporciona el mejor equilibrio entre legibilidad y precisión.
En Secure Content Platform API hay ejemplos muy naturales de acciones específicas en las partes de editor y administrador. El rol EDITOR explica que la persona trabaja con moderación. Pero la “publicación” es una acción poderosa y a veces es útil destacarla con un derecho separado draft:publish. Similarmente con la administración: el rol ADMIN dice “esto es un administrador”, pero el derecho user:manage muestra explícitamente que este administrador puede cambiar roles, bloquear cuentas, etc.
Ejemplo de un “mapa de acceso mixto”: grandes zonas basadas en roles, operaciones individuales basadas en authority. Observa el orden: primero la acción específica, luego la zona más general:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth
// La regla más específica va arriba: publicar requiere un authority separado.
.requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
// Zona más amplia: en la parte editorial en general se permite por rol EDITOR.
.requestMatchers("/api/editor/**").hasRole("EDITOR")
// Zona de administración: acceso por rol ADMIN.
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// Lo demás se deniega, para no dejar un "agujero" por defecto.
.anyRequest().denyAll()
).build();
}
}
¿Por qué es conveniente? Porque puedes dar acceso a una persona a la zona editorial (rol EDITOR), pero no darle la publicación (sin draft:publish). Así podrá ver la cola y, por ejemplo, dejar comentarios (si así lo decides), pero no podrá presionar el “gran botón rojo” de publicación. Y esto ya es un modelo muy parecido a la vida real.
5. Asignación de roles y authorities in-memory
Ahora, una cuestión práctica: si tenemos authorities, ¿cómo asignarlas a los usuarios en el modelo in-memory? Es importante recordar que in-memory no es un “juguete”, sino un modelo educativo. Nuestro objetivo es obtener un conjunto determinista de cuentas para verificar manualmente la matriz de acceso. Por eso creamos deliberadamente varios usuarios “de escenario”: usuario normal, editor, administrador.
Para el caso simple se puede usar roles(...), y para uno más preciso, authorities(...). Aquí es importante el principio mismo: en una misma cuenta pueden coexistir valores tipo role y derechos específicos; authorities(...) simplemente nos obliga a escribir esas cadenas sin añadidos automáticos.
Lo siguiente no es el conjunto final de usuarios del proyecto. Necesitamos un corte local: ver cómo se ve una cuenta de editor si almacenamos tanto cadenas de role como el derecho específico como cadenas exactas.
Ejemplo de usuario in-memory al estilo de “cadenas exactas”:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class InMemoryUsersConfig {
@Bean
InMemoryUserDetailsManager users(PasswordEncoder passwordEncoder) {
// En un proyecto educativo, la contraseña debe estar codificada con el PasswordEncoder configurado en el contexto.
// De lo contrario, Spring Security no arrancará o rechazará el inicio de sesión.
UserDetails editor = User.builder()
.username("eva")
.password(passwordEncoder.encode("eva-pass"))
// Aquí asignamos explícitamente la lista de authorities como cadenas.
// Los roles se definen como ROLE_USER / ROLE_EDITOR, porque authorities(...) no añade prefijo por nosotros.
// Y el derecho específico de publicación se almacena exactamente como "draft:publish".
.authorities("ROLE_USER", "ROLE_EDITOR", "draft:publish")
.build();
// Devolvemos el gestor con un conjunto determinista de usuarios para verificar la matriz de acceso.
return new InMemoryUserDetailsManager(editor);
}
}
Para esta idea, un solo editor es suficiente: en él se ve cómo en una misma lista coexisten cadenas de role y un derecho específico.
Este ejemplo es deliberadamente corto: mostramos el principio mismo — el usuario tiene “marcadores de zona” y un derecho específico. Las cadenas ROLE_USER y ROLE_EDITOR son necesarias porque en authorities(...) escribimos los roles en la forma en que realmente están almacenados para el usuario.
Otro punto metodológico importante: no crees en un proyecto educativo un único “usuario root” que pueda hacer todo. Es útil solo en la primera hora, pero luego arruina el aprendizaje. Necesitas cuentas que demuestren los límites: quién entra en la zona personal, quién en la zona editorial, quién se topa con un 403.
6. Errores típicos al mezclar roles y authorities
Error n.º 1: considerar roles y authorities como sinónimos completos.
Cuando los roles comienzan a usarse como acciones (PUBLISH, REJECT), el modelo crece rápidamente y pierde sentido. Un rol es un grupo de permisos, no una acción concreta. Sustituir uno por otro hace que el sistema sea pesado y difícil de leer.
Error n.º 2: abandonar completamente los roles en favor de las authorities.
Intentar expresar todo mediante derechos específicos conduce a una granularidad excesiva. La configuración se infla y, en lugar de un “mapa de acceso” comprensible, se obtiene un conjunto de reglas inconexas. Los roles son necesarios precisamente para la agregación — para no tener que describir cada acción por separado.
Error n.º 3: dar a las authorities nombres no informativos.
Nombres como WRITE o DO_ACTION no explican nada. Con el tiempo, es imposible entender qué permite exactamente ese derecho. El formato resource:action (draft:publish, order:read) hace que los derechos sean autodocumentados y reduce la confusión.
Error n.º 4: verificar un authority sin haberlo asignado al usuario.
Si en la configuración hay hasAuthority("draft:publish"), pero el usuario no tiene ese derecho, el resultado siempre será 403. Esto no es un “error de Spring”, sino el funcionamiento correcto del sistema. Es importante sincronizar la verificación y la matriz de derechos real.
Error n.º 5: colocar sin criterio comprobaciones de role y de authority sobre la misma decisión de acceso.
El problema no es que en un mismo SecurityFilterChain haya tanto hasRole(...) como hasAuthority(...) — eso es un modelo mixto normal. La confusión comienza cuando para una misma acción, sin una razón clara, se exigen simultáneamente un rol y un derecho específico, y luego nadie puede explicar qué nivel responde de qué. Es mejor acordar de antemano: los roles abren zonas, las authorities detallan operaciones individuales dentro de esas zonas.
GO TO FULL VERSION