Permisos individuales con Authorities en Spring

Resumen

Cuando configuras seguridad con Spring, a veces necesitas que un usuario con rol limitado acceda a un endpoint específico sin darle todo un rol superior. Aquí entra el concepto de authorities en Spring Security, una forma de asignar permisos individuales que conviven con los roles y te permiten abrir accesos puntuales sin romper tu modelo de seguridad.

El caso práctico viene de un Order Controller donde solo los administradores pueden consumir /api/orders, pero queremos que un customer pida una pizza aleatoria desde ese mismo contexto. La solución no es cambiarle el rol, sino darle un permiso específico.

¿Cuál es la diferencia entre role y authority en Spring Security?

Spring trata ambos conceptos de forma casi idéntica por debajo, pero la distinción práctica importa cuando diseñas tu modelo de permisos.

Un role funciona como un grupo de permisos asociado a un tipo de usuario, por ejemplo admin, customer, chef o mesero. Un authority, en cambio, representa un permiso individual y puntual para ejecutar una acción concreta, como RANDOM_ORDER.

¿Cómo distingue Spring un role de un authority? Spring agrega automáticamente el prefijo ROLE_ cuando usas el método roles(). Si ves ROLE_ADMIN es un rol; si ves RANDOM_ORDER sin prefijo, es un authority puntual.

Al construir el usuario en User Security Service, si entras con control al método roles, descubres que internamente Spring le antepone ROLE_ al string que le pasas. Esa es toda la magia.

¿Cómo asignar authorities personalizados a un usuario?

Para mezclar roles y permisos puntuales en el mismo usuario, debes dejar de usar roles() y pasar a authorities(), que recibe objetos de tipo GrantedAuthority.

El plan es construir tú mismo la lista combinando ambos tipos de permisos. Para eso creas un método privado que retorne List<GrantedAuthority>:

  • Recibe el array de roles del usuario.
  • Crea una lista vacía del tamaño de los roles.
  • Recorre los roles con un for each y agrega un SimpleGrantedAuthority con el prefijo ROLE_ por cada uno, replicando lo que Spring hacía automáticamente.
  • Para cada rol, consulta qué authorities adicionales le corresponden y los agrega como SimpleGrantedAuthority planos, sin prefijo.

El método auxiliar getAuthorities recibe un rol puntual y decide qué permisos extra otorgar. Si el rol es admin o customer, retorna un array con RANDOM_ORDER. Si es cualquier otro perfil, como chef o mesero, retorna un array vacío.

java private String[] getAuthorities(String role) { if ("ADMIN".equals(role) || "CUSTOMER".equals(role)) { return new String[]{"RANDOM_ORDER"}; } return new String[]{}; }

Al ejecutar la aplicación y revisar la consola, el contexto de seguridad ya muestra dos permisos separados por coma: ROLE_ADMIN y RANDOM_ORDER. Uno es rol, el otro es permiso puntual.

¿Cómo configurar reglas con hasAuthority en el filter chain?

La configuración de seguridad necesita una regla nueva que use hasAuthority en lugar de hasRole, apuntando al path exacto que queremos liberar.

La regla queda algo así: para /api/orders/random exiges el authority RANDOM_ORDER, mientras que para el resto de /api/orders/** mantienes la exigencia del rol admin. Y aquí viene lo interesante: el orden importa muchísimo.

¿Por qué falla mi authority si ya configuré hasRole antes? Porque el filter chain se evalúa de forma escalonada. Si la regla más amplia (/api/orders/** con rol admin) está antes que la específica del authority, Spring deniega la petición sin llegar a evaluar el permiso puntual.

La regla específica del authority debe ir antes que la regla general del rol. Así Spring valida primero si el path coincide con el endpoint puntual y, en caso afirmativo, deja pasar al usuario que tenga RANDOM_ORDER. Solo si no coincide, baja a la siguiente regla y exige el rol admin.

Verificando el comportamiento desde Postman

Con el orden correcto en las reglas, la prueba confirma el diseño:

  1. Petición a random order sin credenciales devuelve 401.
  2. Con Basic Auth de admin/admin123 devuelve 200.
  3. Con un usuario customer al endpoint random order devuelve 200, gracias al authority puntual.
  4. Con ese mismo customer pidiendo todas las órdenes devuelve 403, porque no tiene el rol admin ni un authority que lo habilite.

Un administrador puede hacer ambas cosas; un customer solo puede pedir la pizza aleatoria. Justo el comportamiento que buscábamos.

¿Cuándo conviene usar authorities en lugar de roles?

Los authorities brillan cuando necesitas abrir una acción concreta a usuarios de distintos roles sin reorganizar toda tu jerarquía de permisos.

En lugar de inventar un rol híbrido o duplicar lógica, asignas un permiso individual que viaja junto con el rol original del usuario. Esto mantiene tu modelo limpio y escalable cuando aparecen excepciones de negocio, como una promoción que cruza tipos de cliente.

Como reto, crea un usuario con perfil chef o mesero, verifica que no reciba el authority RANDOM_ORDER y comprueba que recibe 403 al intentar pedir la pizza aleatoria. ¿Qué otros permisos puntuales agregarías a tu API?