Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan Plus@WithMockUser, @WithAnonymousUser, @WithUserDetails
Spring Security Test proporciona anotaciones especializadas que permiten simular diferentes tipos de usuarios durante las pruebas. Estas anotaciones eliminan la necesidad de configurar manualmente contextos de seguridad complejos, facilitando la verificación del comportamiento de nuestras aplicaciones según el tipo de usuario autenticado.
Simulando usuarios autenticados con @WithMockUser
La anotación @WithMockUser es la forma más directa de simular un usuario autenticado en nuestras pruebas. Esta anotación crea automáticamente un contexto de seguridad con un usuario ficticio que podemos personalizar según nuestras necesidades.
@Test
@WithMockUser
void deberiaPermitirAccesoConUsuarioAutenticado() throws Exception {
mockMvc.perform(get("/dashboard"))
.andExpect(status().isOk())
.andExpect(view().name("dashboard"));
}
Podemos personalizar las características del usuario simulado especificando nombre, roles y autoridades:
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
void deberiaPermitirAccesoAdministrador() throws Exception {
mockMvc.perform(get("/admin/usuarios"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Panel de Administración")));
}
Para casos donde necesitamos autoridades específicas en lugar de roles, utilizamos el atributo authorities
:
@Test
@WithMockUser(authorities = {"READ_PRIVILEGES", "WRITE_PRIVILEGES"})
void deberiaPermitirOperacionesConAutoridades() throws Exception {
mockMvc.perform(post("/api/documentos")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"titulo\":\"Nuevo documento\"}"))
.andExpected(status().isCreated());
}
Probando acceso anónimo con @WithAnonymousUser
La anotación @WithAnonymousUser simula un usuario no autenticado, útil para verificar que las páginas públicas funcionan correctamente y que las protegidas rechazan el acceso apropiadamente.
@Test
@WithAnonymousUser
void deberiaPermitirAccesoAPaginaPublica() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("home"));
}
Esta anotación es especialmente valiosa para probar redirecciones hacia páginas de login:
@Test
@WithAnonymousUser
void deberiaRedirigirALoginDesdeAreaPrivada() throws Exception {
mockMvc.perform(get("/perfil"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
Usando usuarios reales con @WithUserDetails
Cuando necesitamos probar con usuarios que existen realmente en nuestra base de datos o sistema de autenticación, @WithUserDetails carga un usuario a través del UserDetailsService configurado.
Primero, necesitamos un servicio que proporcione los detalles del usuario:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Simulamos la carga desde base de datos
if ("juan.perez".equals(username)) {
return User.builder()
.username("juan.perez")
.password("{noop}password123")
.roles("USER", "MANAGER")
.build();
}
throw new UsernameNotFoundException("Usuario no encontrado: " + username);
}
}
Luego utilizamos la anotación en nuestras pruebas:
@Test
@WithUserDetails("juan.perez")
void deberiaCargarUsuarioRealDesdeServicio() throws Exception {
mockMvc.perform(get("/mi-perfil"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("juan.perez")));
}
Combinando anotaciones con diferentes escenarios
Estas anotaciones pueden aplicarse tanto a métodos individuales como a clases completas. Cuando se aplican a nivel de clase, todos los métodos de prueba heredan la configuración de usuario:
@WithMockUser(roles = "ADMIN")
class AdminControllerTest {
@Test
void deberiaPermitirGestionUsuarios() throws Exception {
// Hereda automáticamente el usuario ADMIN
mockMvc.perform(get("/admin/usuarios"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER") // Sobrescribe la configuración de clase
void deberiaNegarAccesoAUsuarioNormal() throws Exception {
mockMvc.perform(get("/admin/usuarios"))
.andExpect(status().isForbidden());
}
}
Para probar múltiples escenarios de autorización en un mismo método, podemos usar anotaciones repetidas:
@Test
@WithMockUser(roles = "USER")
void deberiaComportarseDiferenteSegunRol() throws Exception {
// Prueba con usuario normal
mockMvc.perform(get("/dashboard"))
.andExpect(status().isOk())
.andExpect(content().string(not(containsString("Panel Admin"))));
}
@Test
@WithMockUser(roles = "ADMIN")
void deberiaComportarseDiferenteSegunRolAdmin() throws Exception {
// Prueba con administrador
mockMvc.perform(get("/dashboard"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Panel Admin")));
}
Estas anotaciones simplifican significativamente las pruebas de seguridad, permitiendo verificar comportamientos específicos según el tipo de usuario sin la complejidad de configurar manualmente los contextos de autenticación.
Testing de formularios con CSRF
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Los formularios web en aplicaciones Spring Boot están protegidos automáticamente contra ataques CSRF (Cross-Site Request Forgery) cuando Spring Security está habilitado. Esta protección requiere que cada formulario incluya un token CSRF válido, lo que presenta desafíos específicos durante las pruebas automatizadas.
Entendiendo la protección CSRF en formularios
Spring Security genera automáticamente un token CSRF único para cada sesión de usuario y lo incluye en los formularios HTML. Cuando el formulario se envía, el servidor verifica que el token recibido coincida con el almacenado en la sesión.
@Controller
public class UsuarioController {
@GetMapping("/registro")
public String mostrarFormularioRegistro(Model model) {
model.addAttribute("usuario", new Usuario());
return "registro";
}
@PostMapping("/registro")
public String procesarRegistro(@ModelAttribute Usuario usuario) {
// Procesar registro del usuario
return "redirect:/login";
}
}
El formulario HTML correspondiente incluye automáticamente el token CSRF:
<form th:action="@{/registro}" method="post" th:object="${usuario}">
<input type="text" th:field="*{nombre}" placeholder="Nombre" />
<input type="email" th:field="*{email}" placeholder="Email" />
<input type="password" th:field="*{password}" placeholder="Contraseña" />
<!-- Spring Security añade automáticamente el token CSRF -->
<button type="submit">Registrarse</button>
</form>
Probando formularios con tokens CSRF válidos
Para probar formularios que requieren protección CSRF, debemos incluir el token en nuestras peticiones de prueba. Spring Security Test proporciona el método csrf()
que genera automáticamente un token válido:
@Test
@WithMockUser
void deberiaPermitirRegistroConTokenCSRF() throws Exception {
mockMvc.perform(post("/registro")
.param("nombre", "Ana García")
.param("email", "ana@ejemplo.com")
.param("password", "password123")
.with(csrf())) // Incluye token CSRF válido
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login"));
}
También podemos probar formularios más complejos que incluyen múltiples campos y validaciones:
@Test
@WithMockUser
void deberiaValidarCamposObligatoriosEnFormulario() throws Exception {
mockMvc.perform(post("/perfil/actualizar")
.param("nombre", "") // Campo vacío para probar validación
.param("email", "email-invalido")
.param("telefono", "123456789")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(view().name("perfil"))
.andExpect(model().hasErrors());
}
Verificando el rechazo sin token CSRF
Es igualmente importante verificar que los formularios rechazan peticiones que no incluyen el token CSRF, asegurando que la protección funciona correctamente:
@Test
@WithMockUser
void deberiaRechazarFormularioSinTokenCSRF() throws Exception {
mockMvc.perform(post("/registro")
.param("nombre", "Usuario Test")
.param("email", "test@ejemplo.com")
.param("password", "password123"))
// Sin .with(csrf())
.andExpect(status().isForbidden());
}
Probando formularios con diferentes tipos de contenido
Los formularios pueden enviar datos en diferentes formatos. Para formularios JSON que también requieren protección CSRF:
@Test
@WithMockUser
void deberiaPermitirActualizacionPerfilJSON() throws Exception {
String jsonUsuario = """
{
"nombre": "Carlos López",
"email": "carlos@ejemplo.com",
"telefono": "987654321"
}
""";
mockMvc.perform(put("/api/perfil")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonUsuario)
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.mensaje").value("Perfil actualizado"));
}
Testing de formularios con archivos
Los formularios que incluyen carga de archivos también necesitan protección CSRF. Podemos probar estos escenarios usando MockMultipartFile:
@Test
@WithMockUser
void deberiaPermitirSubidaArchivoConCSRF() throws Exception {
MockMultipartFile archivo = new MockMultipartFile(
"archivo",
"documento.pdf",
"application/pdf",
"contenido del archivo".getBytes()
);
mockMvc.perform(multipart("/documentos/subir")
.file(archivo)
.param("descripcion", "Documento importante")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/documentos"));
}
Configuración personalizada para pruebas CSRF
En algunos casos, podemos necesitar configurar aspectos específicos del comportamiento CSRF durante las pruebas. Podemos crear métodos auxiliares para simplificar las pruebas repetitivas:
@TestConfiguration
public class TestSecurityConfig {
public static RequestPostProcessor csrfToken() {
return csrf().asHeader(); // Envía CSRF como header en lugar de parámetro
}
}
@Test
@WithMockUser
void deberiaAceptarCSRFComoHeader() throws Exception {
mockMvc.perform(post("/api/configuracion")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"tema\":\"oscuro\"}")
.with(TestSecurityConfig.csrfToken()))
.andExpect(status().isOk());
}
Probando formularios con diferentes roles de usuario
La combinación de protección CSRF y autorización por roles requiere verificar que ambos mecanismos funcionan correctamente:
@Test
@WithMockUser(roles = "ADMIN")
void deberiaPermitirFormularioAdminConCSRF() throws Exception {
mockMvc.perform(post("/admin/configuracion")
.param("configuracion", "valor_admin")
.param("activo", "true")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Configuración guardada")));
}
@Test
@WithMockUser(roles = "USER")
void deberiaRechazarFormularioAdminParaUsuarioNormal() throws Exception {
mockMvc.perform(post("/admin/configuracion")
.param("configuracion", "valor_usuario")
.with(csrf())) // Incluye CSRF pero no tiene permisos
.andExpect(status().isForbidden());
}
La protección CSRF es un elemento fundamental de la seguridad web que debe probarse sistemáticamente para garantizar que nuestros formularios están adecuadamente protegidos contra ataques maliciosos, mientras mantienen la funcionalidad esperada para usuarios legítimos.
Aprendizajes de esta lección de SpringBoot
- Comprender el uso de las anotaciones @WithMockUser, @WithAnonymousUser y @WithUserDetails para simular diferentes tipos de usuarios en pruebas.
- Aprender a personalizar usuarios simulados con roles y autoridades específicas.
- Entender la protección CSRF en formularios web y cómo incluir tokens CSRF válidos en pruebas automatizadas.
- Saber verificar el rechazo de peticiones sin token CSRF para garantizar la seguridad.
- Integrar pruebas de formularios con diferentes tipos de contenido y cargas de archivos bajo protección CSRF.
Completa este curso de SpringBoot y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs