Refinamiento Sprint 2 - Gestión de Usuarios
Visión General
Stack Tecnológico
Backend (Spring Boot)
- Framework: Spring Boot 3.x
- Persistencia: Spring Data JPA + PostgreSQL/MySQL
- Validación: Bean Validation (Hibernate Validator)
- Seguridad: Spring Security + JWT
- Documentación: OpenAPI 3 (Swagger)
- Logs: Logback/SLF4J
Frontend (Next.js)
- Framework: Next.js 14+ (App Router)
- Componentes de UI: React + TypeScript
- Estilos: Tailwind CSS o Material-UI
- Gestión de Estado: React Query/TanStack Query
- Formularios: React Hook Form + Zod validation
- Cliente HTTP: Axios o Fetch API
Estructura de Datos
Entidades Principales
- User: Entidad base para colaboradores y PDMs
- UserType: ENUM (COLLABORATOR, PDM)
- UserStatus: ENUM (ACTIVE, INACTIVE)
Relaciones
- User → User: Relación de autorreferencia (PDM → Colaborador)
APIs REST
Endpoints Principales
GET /api/users- Lista paginada de usuariosPOST /api/users- Creación de usuarioGET /api/users/{id}- Detalles del usuarioPUT /api/users/{id}- Actualización de usuarioDELETE /api/users/{id}- Desactivación de usuarioGET /api/users/pdms- Lista de PDMs disponibles
Patrones
- DTOs: Solicitud/Respuesta separados de las entidades
- Validation: Validación de negocio en la capa de servicio
- Exception Handling: Manejador de excepciones global
- Pagination: Spring Data Pageable
Componentes Frontend
Páginas/Rutas
/admin/users- Lista de usuarios/admin/users/new- Registro de usuario/admin/users/[id]/edit- Edición de usuario
Componentes Principales
- UserList: Tabla con paginación y acciones
- UserForm: Formulario reutilizable (crear/editar)
- UserTypeSelector: Selector con lógica condicional
- PDMSelector: Dropdown de PDMs disponibles
- ConfirmDialog: Modal de confirmación para eliminación
Entendiendo Spring Security
Spring Security es un framework potente y altamente personalizable que proporciona autenticación y control de acceso para aplicaciones Java, especialmente aquellas construidas con el Spring Framework. Ofrece soporte para diversos métodos de autenticación, incluyendo autenticación basada en formularios, autenticación básica, OAuth2 y JWT (JSON Web Tokens), permitiendo a los desarrolladores implementar seguridad de manera flexible y eficaz. Spring Security también permite la definición de reglas de autorización mediante anotaciones, como @PreAuthorize, que facilitan el control de acceso a métodos y recursos basándose en los permisos del usuario. Además, el framework se integra de forma transparente con el modelo de seguridad de Spring, permitiendo la gestión de sesiones y la protección contra vulnerabilidades comunes, como CSRF (Cross-Site Request Forgery) y ataques de fuerza bruta, lo que lo convierte en una elección popular para proteger aplicaciones web y APIs RESTful.
Explicación:
- Cliente: El punto de entrada que realiza una solicitud HTTP.
- OncePerRequestFilter: Un filtro que se invoca para cada solicitud, garantizando que la autenticación y la autorización se procesen.
- Authentication: El objeto que contiene la información del usuario autenticado.
- SecurityContextHolder: Un holder que almacena el contexto de seguridad de la solicitud actual.
- Verificación de Autoridad: El proceso de verificar si el usuario autenticado posee la autoridad necesaria.
- @PreAuthorize: Anotación que protege métodos basándose en las autoridades del usuario.
- Solicitud Procesada: Si el usuario posee la autoridad, la solicitud se procesa normalmente.
- Acceso Denegado: Si el usuario no posee la autoridad, se devuelve un error de acceso denegado.
- Configuración de Seguridad: Donde se definen las configuraciones de Spring Security, incluida la gestión de sesiones.
Observaciones:
- La configuración
sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))indica que la aplicación no mantendrá sesiones y cada solicitud es independiente, lo que es común en APIs RESTful.
Protegiendo Endpoints con HasType
El componente HasType es una anotación personalizada que combina la funcionalidad de Spring Security con la flexibilidad de las anotaciones personalizadas, permitiendo que métodos o clases sean protegidos basándose en las autoridades (permisos) del usuario. Al ser aplicada a un método o clase, esta anotación utiliza la expresión @PreAuthorize para verificar si el usuario autenticado posee la autoridad especificada en el parámetro value. Esto proporciona un control de acceso más granular, permitiendo a los desarrolladores definir fácilmente qué roles o permisos son necesarios para acceder a determinadas funcionalidades de la aplicación, simplificando la implementación de reglas de seguridad y haciendo el código más legible y organizado. Así, HasType puede ser usado para proteger endpoints o servicios, asegurando que solo los usuarios con los permisos adecuados puedan ejecutarlos.
Nota
Para una gestión de acceso más granular, use la anotación @PreAuthorize que permite usar expresiones más avanzadas.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/resource")
public class ResourceController {
@GetMapping("/secure-data")
@HasType("ADMIN") // Solo los usuarios con la autoridad ROLE_ADMIN pueden acceder a este método
public ResponseEntity<String> getSecureData() {
String secureData = "Este es un dato seguro al que solo los administradores pueden acceder.";
return ResponseEntity.ok(secureData); // Devuelve el dato seguro
}
}
Protegiendo Rutas de Next.js con Middlewares
La protección basada en middleware en Next.js es un enfoque que permite implementar lógica de autenticación y autorización de forma centralizada, antes de que las solicitudes lleguen a las rutas específicas de la aplicación. El middleware puede ser utilizado para interceptar solicitudes y verificar si un usuario está autenticado o posee los permisos necesarios para acceder a determinadas páginas o recursos. Esto proporciona una capa adicional de seguridad, garantizando que solo los usuarios autorizados puedan acceder a información sensible o acciones restringidas dentro de la aplicación. Además, la utilización de middleware mejora la organización del código, ya que la lógica de seguridad puede separarse de las funciones de negocio.
Ejemplo:
// middleware.js
import { NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
// Función para verificar si el usuario es ADMIN
export async function middleware(req) {
const token = req.cookies.get('token')?.value; // Suponiendo que el token JWT está almacenado en cookies
if (!token) {
// Si no hay token, redirigir a la página de login
return NextResponse.redirect(new URL('/login', req.url));
}
const decoded = jwt.decode(token); // Decodifica el token sin verificar la firma
// Verifica si el tipo de usuario es ADMIN
if (!decoded || decoded.type !== 'ADMIN') {
return NextResponse.redirect(new URL('/unauthorized', req.url)); // Redirige a la página de acceso no autorizado
}
// Si el tipo es ADMIN, permite el acceso a la ruta
return NextResponse.next();
}
// Configurando el middleware para las rutas /users
export const config = {
matcher: ['/users/:path*'],
};