Refinamiento Sprint 1 - Inicio de sesión de usuarios
Visión General
Una aplicación React y un backend Java que utiliza APIs RESTful, la conexión de Java con una base de datos Postgres a través de JPA, la capa de seguridad de Spring que autentica a los usuarios según sus autoridades y genera tokens JWT a través de la ruta /login.
Explicación:
- Aplicación React: El frontend que realiza peticiones HTTP a la API RESTful.
- API RESTful (Spring Boot): El backend que procesa las peticiones de React e interactúa con la capa de seguridad y la base de datos.
- Capa de Seguridad (Spring Security): Gestiona la autenticación y autorización de los usuarios.
- /login: Endpoint donde los usuarios envían sus credenciales para obtener un token JWT.
- Verifica Autoridad: Confirma si el usuario tiene la autoridad necesaria para acceder a recursos específicos.
- Base de Datos Postgres: Almacena los datos de la aplicación.
- JPA: Utilizado para interactuar con la base de datos a través de entidades.
- Entidades JPA: Representaciones de tablas en la base de datos, que permiten que la aplicación interactúe con los datos de forma orientada a objetos.
- Flujo de Autenticación: El usuario se autentica a través del endpoint
/loginy se genera un token JWT. Este token se envía junto con las peticiones posteriores para acceder a recursos protegidos. - Acceso Permitido/Acceso Denegado: El sistema determina si la petición puede proceder basándose en las autoridades del usuario.
1. Gestión de entorno
Docker
Para ejecutar los sistemas, ya tenemos contenedores Docker configurados en la aplicación. Para saber más sobre Docker, lea nuestra guía de uso de Docker
Docker Compose
Además de las configuraciones de Docker (Dockerfile), tenemos una configuración completa de Docker Compose que permite ejecutar todos los microservicios y sus respectivas bases de datos con un único comando. Tenemos configuraciones tanto para un entorno de desarrollo como para un entorno de producción.
Para saber más sobre Docker Compose, lea nuestra guía de uso de Docker Compose
Comandos para ejecutar el Docker Compose de la aplicación
Para ejecutar una versión compilada como si fuera un entorno productivo, ejecute el comando normalmente:
docker compose up -d
Para ver el progreso de los servicios en ejecución, use el comando de visualización de logs de la consola:
docker compose logs -f
Para ejecutar un entorno de desarrollo utilizando Docker, ejecute el comando a continuación. Se ha añadido a este proyecto una configuración exclusiva para desarrollo, que comparte su carpeta local con los contenedores y, por lo tanto, escucha sus modificaciones y actualiza el contenedor automáticamente.
docker compose -f docker-compose-dev.yml -d
Ejecutando comandos dentro del contenedor
El comando docker exec desempeña un papel fundamental en la interacción con contenedores en ejecución, permitiendo que los desarrolladores y administradores del sistema ejecuten comandos adicionales dentro de un contenedor ya activo. Esta funcionalidad es especialmente útil para tareas de administración, depuración y mantenimiento, ya que permite acceder al terminal de un contenedor o ejecutar scripts y procesos sin la necesidad de reiniciar el contenedor o crear una nueva instancia. Por ejemplo, al usar docker exec -it <container_id> /bin/bash, el usuario puede abrir una sesión de shell interactiva dentro del contenedor, facilitando la inspección del sistema de archivos, la verificación de logs o la modificación de configuraciones en tiempo real. Esta capacidad de interactuar directamente con el ambiente del contenedor es esencial para la gestión eficiente de aplicaciones en contenedores, permitiendo resolver problemas rápidamente y realizar actualizaciones o ajustes según sea necesario.
2. Historia de registro del usuario ADMIN
Esta historia requiere la creación de la entidad de usuario con el campo "type" que indica el tipo de usuario. Este campo nos ayudará a identificar si el usuario es del tipo ADMIN, que es el tipo de usuario que puede registrar a otros usuarios.
Nota
Este es solo un modelo ilustrativo; en la práctica, se necesitarán más campos.
Aplicación con línea de comandos
En una aplicación Spring, también estamos usando el modelo estándar de ejecución de Java, es decir, el método main recibe los argumentos normalmente y decidir si se ejecuta o no la aplicación Spring puede ser opcional:
@SpringBootApplication
public class UsermanagementApplication {
public static void main(String[] args) {
if (args.length > 0) {
switch (args[0]) {
case "registrar:admin":
break;
}
} else {
SpringApplication.run(UsermanagementApplication.class, args);
}
}
}
CommandLineRunner
CommandLineRunner es una interfaz de Spring que permite la ejecución de código específico después de la inicialización de la aplicación, siendo llamada automáticamente por Spring Boot cuando la aplicación se inicia. Al implementar la interfaz CommandLineRunner, los desarrolladores pueden definir la lógica que debe ejecutarse justo después de que se cargue el contexto de la aplicación, lo que es útil para tareas como la inicialización de datos, la ejecución de verificaciones de configuración o la configuración de servicios. El método run(String... args) es donde se coloca esta lógica, y los argumentos pasados a la aplicación pueden ser accedidos a través de este método, permitiendo personalizar el comportamiento de la aplicación con base en los parámetros de inicialización.
En nuestra aplicación, podemos usarlo para registrar al usuario admin. Aunque este no sea el papel exacto de esta interfaz, si usamos System.exit podemos hacer el registro del admin y detener la aplicación. En este caso, podemos realizar el registro siempre que hayamos recibido los parámetros necesarios en la línea de comandos:
@Component
public class AdminRegistrationCommandLineRunner implements CommandLineRunner {
@Autowired
private UserService userService;
@Autowired
private ApplicationContext context;
@Override
public void run(String... args) throws Exception {
// Verifica si se invocó el comando que queríamos
if (args.length == 0 || !args[0].equals("register:admin")) {
// si no es el caso, significa que estamos ejecutando la aplicación normalmente, simplemente sal del método
return;
}
try {
String nombre = args[1];
String email = args[2];
String contrasena = args[3];
// llamada de método ilustrativa
userService.cadastrarUsuario(nombre, email, contrasena);
System.exit(SpringApplication.exit(context, () -> 0));
} catch (Exception e) {
System.err.println("❌ Error al registrar el usuario: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
}
Cifrado de contraseñas
Spring Security proporciona soporte integrado para el cifrado de contraseñas usando el algoritmo BCrypt, que es un enfoque seguro y ampliamente utilizado para proteger las contraseñas de los usuarios en las aplicaciones. BCrypt no solo cifra la contraseña, sino que también añade un "salt" aleatorio, lo que hace que la aplicación sea más resistente a ataques de fuerza bruta y de diccionario. Para usar BCrypt, puede inyectar una instancia de PasswordEncoder en su clase de servicio o controlador y utilizar el método encode() para cifrar la contraseña del usuario al registrarlo y el método matches() para verificar la contraseña al autenticar al usuario.
Ejemplo de Uso de BCrypt en Spring
Aquí hay un ejemplo de cómo usar BCrypt para cifrar contraseñas de usuarios en una aplicación Spring:
- Configurar el PasswordEncoder:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // Define el codificador para BCrypt
}
}
- Usar el PasswordEncoder en el Servicio de Usuario:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class PasswordManager {
@Autowired
private PasswordEncoder passwordEncoder;
public String registerUser(String username, String password) {
// Cifra la contraseña usando BCrypt
String encryptedPassword = passwordEncoder.encode(password);
return encryptedPassword;
}
public boolean validateUser(String rawPassword, String storedEncryptedPassword) {
// Verifica si la contraseña proporcionada coincide con la contraseña cifrada almacenada
return passwordEncoder.matches(rawPassword, storedEncryptedPassword);
}
}
Explicación del Código:
- Configuración del PasswordEncoder: La clase
SecurityConfigdefine un bean del tipoPasswordEncoder, configurado para usar BCrypt. - Registro de Usuario: En el método
registerUserde la clasePasswordManager, la contraseña del usuario se cifra usandopasswordEncoder.encode(password), y la contraseña cifrada puede ser almacenada en la base de datos. - Validación de Contraseña: El método
validateUserusapasswordEncoder.matches(rawPassword, storedEncryptedPassword)para verificar si la contraseña proporcionada por el usuario coincide con la contraseña cifrada almacenada.
3. Historias de Inicio de Sesión y Filtro (middleware) de protección de rutas
Protegiendo endpoints con la librería de seguridad
Habilitando el módulo
Los proyectos incluidos en el desafío ya tienen la librería habilitada, solo necesita agregarla al pom.xml
<dependencies>
<dependency>
<groupId>com.ciandt.nextgen25</groupId>
<artifactId>security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
Habilitar la funcionalidad en el código
Simplemente incluya la anotación @EnableJwtSecurity en una clase de configuración.
@Configuration
@EnableJwtSecurity
public class SecurityConfig
{
}
Generación de token JWT usando JwtTokenService
El JwtTokenService se utiliza en una aplicación Spring para gestionar la autenticación basada en tokens JWT (JSON Web Tokens), permitiendo un enfoque seguro y escalable para controlar el acceso a los recursos. Al autenticar a un usuario, el servicio genera un token que contiene información relevante, como el correo electrónico del usuario y sus permisos, firmado con una clave secreta. Este token se devuelve al cliente, que lo utiliza en peticiones posteriores, enviándolo en el encabezado de autorización. El JwtTokenService también valida el token en cada petición protegida, garantizando que el usuario esté autenticado y autorizado para acceder a los recursos solicitados. Este enfoque favorece la construcción de APIs RESTful, donde la statelessness (ausencia de estado) es esencial, ya que cada petición puede ser autenticada de forma independiente a través del token.
@PostMapping("/auth/login")
public ResponseEntity<String> login(@RequestBody UserLoginRequest userLoginRequest) {
// Autentica al usuario con las credenciales proporcionadas
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userLoginRequest.getUsername(), userLoginRequest.getPassword())
);
// Genera un token JWT después de la autenticación exitosa
String token = jwtTokenService.generateToken(userLoginRequest);
return ResponseEntity.ok(token); // Devuelve el token en la respuesta
}
Guía de referencias
A continuación, se presentan los documentos más importantes para leer en este sprint:
Palabras Clave, Recursos y Ejemplos de Prompt
-
React
- Enlace de Referencia: Documentación de React
- Prompt de IA: "Genera un ejemplo de un componente funcional en React que muestre una lista de usuarios."
-
Java
- Enlace de Referencia: Documentación de Java
- Prompt de IA: "Crea un ejemplo simple de una clase Java con métodos para registrar y listar usuarios."
-
Spring Boot
- Enlace de Referencia: Documentación de Spring Boot
- Prompt de IA: "Genera un ejemplo de una aplicación Spring Boot que configure un endpoint REST para el inicio de sesión."
-
JPA (Java Persistence API)
- Enlace de Referencia: Documentación de JPA
- Prompt de IA: "Crea un ejemplo de una entidad JPA para la clase
Usuariocon los campos nombre, email y contraseña."
-
PostgreSQL
- Enlace de Referencia: Documentación de PostgreSQL
- Prompt de IA: "Genera un script SQL para crear una tabla de usuarios en PostgreSQL."
-
Spring Security
- Enlace de Referencia: Documentación de Spring Security
- Prompt de IA: "Crea un ejemplo de configuración de Spring Security para la autenticación usando JWT."
-
JWT (JSON Web Token)
- Enlace de Referencia: Introducción a JWT
- Prompt de IA: "Genera un ejemplo de cómo crear y validar un token JWT en una aplicación Spring Boot."
-
Docker
- Enlace de Referencia: Documentación de Docker
- Prompt de IA: "Genera un Dockerfile para una aplicación Spring Boot."
-
Docker Compose
- Enlace de Referencia: Documentación de Docker Compose
- Prompt de IA: "Crea un archivo
docker-compose.ymlpara orquestar una aplicación Spring Boot con una base de datos PostgreSQL."
-
CommandLineRunner
- Enlace de Referencia: Spring Boot CommandLineRunner
- Prompt de IA: "Genera un ejemplo de uso de
CommandLineRunnerpara inicializar datos en una aplicación Spring Boot."
-
BCrypt
- Enlace de Referencia: Documentación de BCrypt en Spring Security
- Prompt de IA: "Crea un ejemplo de cómo usar BCrypt para cifrar contraseñas en una aplicación Spring Boot."
-
Middleware (Next.js)
- Enlace de Referencia: Middleware en Next.js
- Prompt de IA: "Genera un ejemplo de middleware en Next.js que proteja una ruta con autenticación JWT."
-
API RESTful
- Enlace de Referencia: Conceptos de API RESTful
- Prompt de IA: "Crea un ejemplo de una API RESTful en Spring Boot que implemente operaciones CRUD para usuarios."
-
Docker Networking
- Enlace de Referencia: Docker Networking
- Prompt de IA: "Genera un ejemplo de configuración de red en Docker para la comunicación entre contenedores."
-
Entidades JPA
- Enlace de Referencia: JPA Entities
- Prompt de IA: "Crea un ejemplo de entidad JPA para un objeto
Usuariocon las anotaciones adecuadas."
-
Exposición de Puertos en Docker
- Enlace de Referencia: Exposición de Puertos en Docker
- Prompt de IA: "Crea un ejemplo de comando Docker para iniciar un contenedor, exponiendo el puerto 8080 del host al puerto 80 del contenedor."
