Testing con Spring Security Test

Intermedio
SpringBoot
SpringBoot
Actualizado: 13/06/2025

¡Desbloquea el curso de SpringBoot completo!

IA
Ejercicios
Certificado
Entrar

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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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

⭐⭐⭐⭐⭐
4.9/5 valoración