Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado planificar permisos y preparar el entorno de seguridad. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
El usuario ya se autentica contra PostgreSQL. Hoy aplicarás permisos sobre recursos concretos. RBAC significa permisos basados en roles; una comprobación de propiedad añade la relación entre el usuario y el registro solicitado. Conocer un id no concede acceso a ese recurso.
Roles globales frente a Permisos atómicos
En aplicaciones en crecimiento existen dos formas de modelar la autorización:
- Usuario
- Roles (ROLE_ADMINISTRADOR, ROLE_DESARROLLADOR)
- Permisos Atómicos (PROYECTO_BORRAR, TAREA_EDITAR)
- Operación protegida
- Roles (
hasRole): Representan el cargo de una persona en la organización. Por convención en Spring Security llevan el prefijoROLE_en la base de datos, pero en el código se evalúan sin él:hasRole('ADMINISTRADOR')comprueba internamente si el usuario posee la autoridadROLE_ADMINISTRADOR.
- Autoridades / Permisos atómicos (
hasAuthority): Representan una acción puntual sobre un recurso (PROYECTO_WRITE,TAREA_DELETE,INFORME_EXPORTAR).- Permiten construir sistemas de permisos ultra-flexibles donde los roles son agrupaciones de permisos configurables en base de datos.
Seguridad en rutas URL frente a Seguridad en métodos con @PreAuthorize
Podemos aplicar reglas de autorización en dos capas complementarias:
| Estrategia | Dónde se define | Sintaxis típica | Ventajas y uso recomendado |
|---|---|---|---|
| Seguridad de Rutas (HTTP Filter) | En SecurityConfig dentro de SecurityFilterChain. |
.requestMatchers(HttpMethod.DELETE, "/api/v1/proyectos/**").hasRole("ADMINISTRADOR") |
Primera barrera perimetral: rechaza peticiones no autorizadas antes de que lleguen al controlador. |
| Seguridad de Métodos (@PreAuthorize) | Sobre métodos de controladores o clases @Service. |
@PreAuthorize("hasRole('ADMINISTRADOR')")@PreAuthorize("hasRole('DEV') and #tarea.autor == authentication.name") |
Seguridad en profundidad: permite evaluar reglas de negocio complejas, parámetros del método (#id) y expresiones de propiedad (SpEL). |
La falacia de la seguridad por ocultación
Uno de los errores más peligrosos de los desarrolladores que vienen del mundo frontend es pensar que la seguridad consiste en esto:
<!-- CUIDADO: Esto es experiencia de usuario, NO es seguridad -->
<button *ngIf="usuario.rol === 'ADMIN'" (click)="eliminarProyecto(id)">
Eliminar proyecto
</button>
Ocultar ese botón es una buena práctica de diseño de interfaces: a un usuario normal no le muestras botones que no puede usar.
Pero confundir eso con seguridad es un error catastrófico:
- Cualquier usuario puede abrir la consola de DevTools (
F12), inspeccionar el DOM y eliminar el atributodisabledo hacer visible el botón en 3 segundos. - Cualquier usuario puede abrir Bruno, Postman o una consola con
curly lanzar directamente unDELETE http://localhost:8080/api/v1/proyectos/1.
La ley del servidor como frontera única
El cliente web es un entorno bajo el control absoluto del usuario (y del atacante).
La única frontera real de seguridad de un sistema es el backend. Todo endpoint debe comprobar permisos en el servidor en cada petición, asumiendo siempre que el cliente puede ser malicioso.
Pruebas de seguridad con MockMvc y @WithMockUser
Para garantizar que nuestros endpoints están blindados y que ningún refactor futuro rompa las reglas de seguridad, escribimos pruebas automáticas con @WithMockUser:
@Test
@WithMockUser(username = "dev1", roles = {"DESARROLLADOR"})
void eliminarProyecto_conRolDesarrollador_devuelve403Forbidden() throws Exception {
mockMvc.perform(delete("/api/v1/proyectos/1"))
.andExpect(status().isForbidden());
}
La anotación @WithMockUser:
- Inyecta un
Authenticationen elSecurityContextHolderantes de que el filtro de seguridad ejecute la petición. - Permite probar autorizaciones (
hasRole,@PreAuthorize) de forma instantánea sin necesidad de crear usuarios en PostgreSQL ni generar hashes BCrypt.
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Retomar el proyecto y preparar la comprobación
- Abre la matriz de permisos, los métodos del servicio y SecurityConfig. Prepara dos usuarios del mismo rol y un recurso que pertenezca solo a uno.
- Añade una cuenta con otro rol para contrastar permisos de función. Guarda peticiones separadas por identidad para evitar reutilizar credenciales sin darte cuenta.
- Localiza las pruebas de controlador y la dependencia de test de seguridad que se añade en el taller.
Paso 2 · Implementar la Matriz RBAC en la aplicación
Si ManejadorDeErrores conserva un método para Exception, añade otro para denegaciones de Spring Security, importando org.springframework.security.access.AccessDeniedException. Así un permiso rechazado dentro del controlador mantiene el estado 403.
@ExceptionHandler(AccessDeniedException.class)
public ProblemDetail permisoDenegado(AccessDeniedException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN,
"No tienes permiso para realizar esta operación");
}
Las peticiones rechazadas antes del controlador se responden desde la cadena de seguridad; las configuraremos allí al integrar JWT.
Añade @EnableMethodSecurity a la SecurityConfig existente e incorpora las reglas conservando las rutas públicas ya acordadas. En los controladores añade @PreAuthorize sobre los métodos existentes, sin sustituir sus cuerpos por los puntos suspensivos del esquema. Crea después TareaSecurityService en un paquete bajo el principal; su nombre de bean debe coincidir con la expresión. En TareaController, que ya tiene el prefijo /api/v1/tareas, la anotación de modificación utiliza /{id}, no /tareas/{id}.
package com.ejemplo.gestor.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Habilita anotaciones @PreAuthorize y @Secured en toda la aplicación
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// Lo único público sigue siendo la documentación
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
// Borrar un proyecto es la operación irreversible de la matriz
.requestMatchers(HttpMethod.DELETE, "/api/v1/proyectos/**").hasRole("ADMINISTRADOR")
// Gestión de usuarios: reservada estrictamente a Administradores
.requestMatchers("/api/v1/usuarios/**").hasRole("ADMINISTRADOR")
// Todo lo demás requiere que el usuario esté al menos autenticado
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
- Por qué hay dos sitios donde escribir reglas
- El
filterChaindecide por ruta y método HTTP, antes de que Spring sepa siquiera qué controlador va a atender la petición.@PreAuthorizedecide por método Java, cuando ya conoce los argumentos. La primera es una valla perimetral; la segunda, una cerradura en cada puerta. - Cuál usar
- Todo lo que se pueda expresar como «esta ruta con este verbo es solo para este rol» va en el
filterChain: se rechaza antes y en un solo sitio. En cuanto la regla necesita mirar el dato concreto —«solo si esta tarea es tuya»— no hay ruta que la exprese y hace falta@PreAuthorize. - Qué pasa si las dos hablan de lo mismo
- Se aplican las dos, y gana la más restrictiva, porque la del filtro se evalúa primero e interrumpe la cadena. Mantener ambas no constituye un error, sino defensa en profundidad; conserva no obstante una sola como fuente de verdad para cada regla, o acabarás cambiando una y no la otra.
- El prefijo
ROLE_, de una vez - En la base de datos guardas
ROLE_ADMINISTRADOR. EnhasRole()escribes'ADMINISTRADOR', sin prefijo, porque el método lo añade solo. Si escribeshasRole('ROLE_ADMINISTRADOR'), Spring buscaráROLE_ROLE_ADMINISTRADORy nadie pasará nunca. La versión sin magia eshasAuthority('ROLE_ADMINISTRADOR'), que compara literalmente.
Decoramos los métodos de escritura con anotaciones declarativas:
@RestController
@RequestMapping("/api/v1/proyectos")
public class ProyectoController {
private final ProyectoService proyectoService;
public ProyectoController(ProyectoService proyectoService) {
this.proyectoService = proyectoService;
}
// Creación permitida a Jefes de Proyecto y Administradores
@PostMapping
@PreAuthorize("hasAnyRole('JEFE_PROYECTO', 'ADMINISTRADOR')")
public ResponseEntity<ProyectoResponse> crear(@Valid @RequestBody ProyectoRequest request) {
// ...
}
// Borrado de proyectos: reservado exclusivamente a Administradores
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMINISTRADOR')")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
proyectoService.eliminarProyecto(id);
return ResponseEntity.noContent().build();
}
}
Podemos condicionar la edición de una tarea a que el usuario sea el autor de la misma:
// El usuario solo puede editar la tarea si es Administrador O si él mismo es el asignado
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMINISTRADOR') or @tareaSecurityService.esAsignado(#id, authentication.name)")
public ResponseEntity<TareaResponse> actualizarTarea(
@PathVariable Long id,
@Valid @RequestBody TareaRequest request) {
// ...
}
Donde tareaSecurityService es un componente Spring que comprueba la base de datos:
@Component
public class TareaSecurityService {
private final TareaRepository tareaRepository;
public TareaSecurityService(TareaRepository tareaRepository) {
this.tareaRepository = tareaRepository;
}
public boolean esAsignado(Long tareaId, String username) {
return tareaRepository.findById(tareaId)
.map(t -> t.getAsignadoA() != null && t.getAsignadoA().getUsername().equals(username))
.orElse(false);
}
}
Paso 3 · Simulación de matriz de permisos en Bruno
Con los usuarios cargados en PostgreSQL (admin con ROLE_ADMINISTRADOR y dev1 con ROLE_DESARROLLADOR), ejecuta estas pruebas en Bruno:
- Borrado por Administrador:
- Petición:
DELETE /api/v1/proyectos/1. - Auth: Basic con
admin/Password123!. - Resultado esperado: Código
204 No Content. Operación permitida.
- Petición:
- Borrado fraudulento por Desarrollador:
- Misma petición:
DELETE /api/v1/proyectos/1. - Auth: Basic con
dev1/Password123!. - Resultado esperado: Código
403 Forbidden. Spring Security intercepta la llamada, comprueba quedev1carece deROLE_ADMINISTRADORy deniega el acceso sin ejecutar el método del controlador.
- Misma petición:
- Acceso anónimo a la misma ruta:
- Misma petición sin credenciales en la pestaña Auth.
- Resultado esperado: Código
401 Unauthorized.
Paso 4 · Si algo no sale como dice el guion
| Síntoma | Causa casi segura | Qué mirar |
|---|---|---|
@PreAuthorize no hace absolutamente nada |
Falta @EnableMethodSecurity |
Va sobre la clase SecurityConfig, junto a @EnableWebSecurity |
Todo el mundo recibe 403, incluso admin |
Prefijo duplicado | ¿Has escrito hasRole('ROLE_ADMINISTRADOR')? Quita el ROLE_ |
admin recibe 403 y el rol está bien escrito |
La autoridad guardada no lleva el prefijo | En data.sql la columna debe decir ROLE_ADMINISTRADOR, no ADMINISTRADOR |
EL1008E: Property or field 'tareaSecurityService' cannot be found |
El bean no existe con ese nombre | El nombre en la expresión es el del bean: @Component sobre TareaSecurityService lo registra como tareaSecurityService, con minúscula inicial |
El 403 llega, pero el método se ejecutó igualmente |
Estás anotando un método privado, o llamándolo desde la misma clase | Las anotaciones de seguridad funcionan por proxy: solo actúan en llamadas públicas que entran desde fuera del bean |
Paso 5 · Trasladar la matriz entera al código
Hasta ahora la matriz de la sesión 35 era un documento. Aquí se convierte en código ejecutable.
- Permite que
ROLE_DESARROLLADOR,ROLE_JEFE_PROYECTOyROLE_ADMINISTRADORpuedan crear tareas sobre un proyecto existente (POST /api/v1/proyectos/{id}/tareas). - Restringe el borrado de tareas (
DELETE /api/v1/tareas/{id}) aROLE_JEFE_PROYECTOyROLE_ADMINISTRADOR. - Aplica la fila más incómoda de la matriz:
POST /api/v1/proyectoses deJEFE_PROYECTOyADMINISTRADOR, peroDELETE /api/v1/proyectos/{id}es solo deADMINISTRADOR. UnJEFE_PROYECTOque borra debe recibir403, no204. - Decide, y anota por qué, dónde pones cada una de esas tres reglas: en el
filterChaino en@PreAuthorize. No hay una respuesta única, pero sí tiene que haber un criterio. - Comprueba con tu cliente HTTP la matriz completa: son 9 filas × 4 columnas = 36 comprobaciones. Guárdalas en la carpeta
09-seguridadcon un nombre que diga qué esperas, del tipodev1-borra-proyecto-403. - Marca en la tabla de la sesión 35, con un ✔, cada casilla que ya devuelve lo que decía. Las que no coincidan son tu lista de tareas: o está mal el código, o está mal la matriz, y decidir cuál de las dos es parte del ejercicio.
- Cómo saber que lo has terminado
- Las 36 casillas de la matriz responden lo que la tabla dice. En particular: un
DESARROLLADORnunca ve un401(ya está identificado, sus rechazos son403), y unJEFE_PROYECTOpuede crear proyectos pero no borrarlos.
Proteger endpoints
Paso 6 · Batería de tests de seguridad para ProyectoController
Añade spring-security-test con scope test una sola vez. Crea la clase de pruebas, importa SecurityConfig y declara mocks para todos los colaboradores de su controlador. Copia los métodos de test dentro de esa clase. @WithMockUser prepara la identidad; no crea una fila de usuario en PostgreSQL. Cuando pruebes reglas de propiedad, prepara además la respuesta del colaborador que comprueba esa propiedad y verifica que un usuario distinto obtiene el rechazo esperado.
@WithMockUser no viene con el starter de test. Añade a tu pom.xml:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
package com.ejemplo.gestor;
import com.ejemplo.gestor.config.SecurityConfig;
import com.ejemplo.gestor.controller.ProyectoController;
import com.ejemplo.gestor.service.ProyectoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ProyectoController.class)
@Import(SecurityConfig.class) // sin esta línea, tus reglas NO se aplican
class ProyectoSecurityTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProyectoService proyectoService;
La línea que decide si este test sirve para algo
@WebMvcTest carga controladores, no clases de configuración cualesquiera. Tu SecurityConfig es una @Configuration normal, así que no entra: el test se ejecutaría contra la cadena de seguridad por defecto de Spring Boot, sin tus requestMatchers y sin @EnableMethodSecurity.
El resultado es el peor posible: el test de 401 pasa igualmente (la cadena por defecto también exige autenticación), pero el de 403 devuelve 204 y falla, o peor, pasa por una razón equivocada. Tendrías una suite verde que no está probando tus reglas. @Import(SecurityConfig.class) es lo que hace que el test hable de tu configuración y no de otra.
Verificamos que si no hay identidad en el contexto, el peralte de seguridad intercepta la llamada:
@Test
void eliminarProyecto_sinAutenticar_devuelve401Unauthorized() throws Exception {
mockMvc.perform(delete("/api/v1/proyectos/1"))
.andExpect(status().isUnauthorized());
// Verificamos que la lógica de negocio jamás fue invocada
verify(proyectoService, never()).eliminarProyecto(anyLong());
}
Verificamos que un usuario identificado con rol DESARROLLADOR recibe 403:
@Test
@WithMockUser(username = "juan.dev", roles = {"DESARROLLADOR"})
void eliminarProyecto_conRolDesarrollador_devuelve403Forbidden() throws Exception {
mockMvc.perform(delete("/api/v1/proyectos/1"))
.andExpect(status().isForbidden());
verify(proyectoService, never()).eliminarProyecto(anyLong());
}
Verificamos que el rol ADMINISTRADOR ejecuta la acción con éxito:
@Test
@WithMockUser(username = "admin.jefe", roles = {"ADMINISTRADOR"})
void eliminarProyecto_conRolAdministrador_devuelve204NoContent() throws Exception {
doNothing().when(proyectoService).eliminarProyecto(1L);
mockMvc.perform(delete("/api/v1/proyectos/1"))
.andExpect(status().isNoContent());
verify(proyectoService, times(1)).eliminarProyecto(1L);
}
}
Paso 7 · Ejecutar la suite de seguridad en terminal
Ejecuta las pruebas desde la consola de Maven:
./mvnw test -Dtest=ProyectoSecurityTest
Comprueba en la salida:
- Los 3 tests pasan al 100 % en verde en menos de 1 segundo.
- Queda demostrado que la interfaz de cliente no condiciona la autorización: un usuario no administrador no puede borrar un proyecto en el servidor.
Un test de seguridad que nunca ha fallado no ha demostrado nada todavía. Rómpelo a propósito y míralo caer:
- Comenta la línea
@PreAuthorize("hasRole('ADMINISTRADOR')")del métodoeliminar. - Ejecuta
./mvnw test -Dtest=ProyectoSecurityTest. - Resultado esperado: el test del
403falla conStatus expected:<403> but was:<204>. Ahí está la regresión que este test existe para cazar. - Descomenta la anotación y vuelve a ejecutar. Verde otra vez.
- Repite la jugada quitando
@Import(SecurityConfig.class)de la clase de test. Verás el mismo fallo, y esa es la lección: un test verde solo vale si estás seguro de contra qué configuración corre.
Paso 8 · Si algo no sale como dice el guion
| Síntoma | Causa casi segura | Qué mirar |
|---|---|---|
cannot find symbol: class WithMockUser |
Falta la dependencia | spring-security-test con <scope>test</scope> en el pom.xml |
El test de 403 recibe 204 |
Tus reglas no están cargadas | Falta @Import(SecurityConfig.class), o falta @EnableMethodSecurity en SecurityConfig |
El test de 401 recibe 403 |
El usuario anónimo se considera autenticado | ¿Has puesto @WithMockUser a nivel de clase? Solo debe estar en los métodos que lo necesitan |
POST y DELETE reciben 403 en todos los tests |
CSRF activo dentro del test | O usas .with(csrf()) en la petición, o mantienes csrf.disable() en la config que importas |
No qualifying bean of type UserDetailsService |
Tu SecurityConfig arrastra dependencias que el slice no carga |
Añade @MockBean private CustomUserDetailsService userDetailsService; a la clase de test |
Paso 9 · Batería de tests de seguridad para Tareas
Aplica el mismo patrón para proteger la creación y modificación de tareas:
- Crea
TareaSecurityTestcon@WebMvcTest(TareaController.class)y@Import(SecurityConfig.class). - Escribe un test que verifique que un usuario anónimo recibe
401al intentar crear una tarea (POST /api/v1/proyectos/1/tareas). - Escribe un test con
@WithMockUser(roles = "DESARROLLADOR")que confirme que un desarrollador sí puede crear tareas (código201). - Escribe un test que verifique que el borrado de tareas devuelve
403para@WithMockUser(roles = "DESARROLLADOR")y204para@WithMockUser(roles = "JEFE_PROYECTO"). - Añade a todos los tests de rechazo la verificación
verify(tareaService, never()).…. Comprobar el código de estado demuestra que el cliente recibió un403; comprobar que el servicio nunca se llamó demuestra que la operación no llegó a ocurrir. No son lo mismo, y solo la segunda descarta un borrado que sucedió y luego respondió mal. - Cuenta cuántas casillas de la matriz de la sesión 35 cubre ya tu suite. Si has hecho los pasos 2 a 4, son 6 de 36. Anota en tu cuaderno cuáles faltan: la sesión 45 va a partir de ese inventario.
- Cómo saber que lo has terminado
./mvnw testpasa en verde; cada test de rechazo verifica además que el servicio no se invocó; y has visto al menos un test tuyo fallar en rojo al quitarle la anotación de seguridad que protege.
Paso 10 · Comprobar y registrar el resultado del proyecto
- Prueba una acción como propietario y como otro usuario del mismo rol. Comprueba tanto lectura como modificación y que los rechazos no alteran datos.
- Ejecuta los tests de identidad ausente, rol insuficiente y acceso permitido; sus expectativas deben reflejar tu matriz y la política de errores documentada.
Ampliación si has completado el trabajo
Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.
Reto · Excepciones de acceso denegado personalizadas
Por defecto, cuando un usuario autenticado recibe un 403 Forbidden, Spring Security no devuelve un formato amigable.
Implementa un AccessDeniedHandler personalizado:
- Crea la clase
CustomAccessDeniedHandlerque implementeAccessDeniedHandler. - Emite una respuesta estándar RFC 7807 con código
403, título “Acceso Denegado” y detalle indicando que el rol actual no dispone de los privilegios requeridos. - Regístralo en
SecurityConfigbajo.exceptionHandling(ex -> ex.accessDeniedHandler(...)).
Formato de entrega
Incluye esta explicación en el registro de la sesión dentro del repositorio de GitHub, junto al código y las comprobaciones. La entrega es el enlace al repositorio y al commit de la sesión.
SecurityFilterChain y probadas con Bruno.@PreAuthorize aplicada en controladores con hasRole y hasAnyRole diferenciando 401 y 403.AccessDeniedHandler emitiendo respuestas RFC 7807 ante 403.Ver respuestas
1 · Porque Spring Security añade automáticamente el prefijo ROLE_ por convención histórica para diferenciar roles de permisos simples.
2 · Permite colocar la regla de seguridad junto al método que ejecuta la acción, facilitando la legibilidad, y permite acceder a los parámetros del método y a la lógica de dominio.
3 · Código HTTP 403 Forbidden.
4 · Para hacer referencia a un argumento formal que recibe el método anotado (evaluación contextual de parámetros).
Reto · Pruebas de seguridad basadas en atributos con SpEL
En el trabajo anterior definimos que un desarrollador solo puede editar las tareas que tiene asignadas a su nombre.
¿Cómo se prueba esa regla con MockMvc?
- Escribe un test simulando a
@WithMockUser(username = "carlos"). - Simula una tarea cuyo responsable es
"maria". - Verifica que al intentar hacer
PUT /api/v1/tareas/10el sistema responde con403 Forbidden. - Repite la prueba con una tarea asignada a
"carlos"y confirma que responde con200 OK.
@WebMvcTest y @WithMockUser verificando casos 401, 403 y 204.Ver respuestas
1 · Porque el cliente corre en el dispositivo del usuario, quien puede inspeccionar el DOM, modificar el script o lanzar peticiones HTTP directas por consola evadiendo cualquier restricción visual.
2 · El test normal evalúa rutas y serialización; @WithMockUser inyecta un contexto de seguridad previo para comprobar si los filtros y @PreAuthorize permiten o bloquean la petición.
3 · Para demostrar fehacientemente que la seguridad interceptó la llamada en la frontera de red y que ninguna lógica de negocio llegó a ejecutarse en el servidor.
4 · Mediante el atributo roles = {"ROL_A", "ROL_B"} dentro de la anotación.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
Los tests demuestran que un usuario no lee ni modifica recursos ajenos fuera de la política del producto.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.