====== 08 - Login con JWT ====== La tienda ya vende con transacciones; le falta saber **quién compra y quién administra**. Este hito aplica el [[temario:06-login-jwt|tema 06 — Login con JWT]]: el módulo **users** con registro y login, el par de tokens — access **JWT** de vida corta y refresh **opaco** persistido — y la validación stateless en un filtro propio. La autorización declarativa llega en el hito siguiente. ^ Pieza ^ Cambio en este hito ^ | Contrato | v1.0.0: registro y login; el pedido toma la identidad del token | | Estructura | Módulo **users** nuevo, que nace ya con JPA | | Mecanismo | Access **JWT** + refresh opaco con rotación; filtro Servlet propio | ===== 1. El contrato evoluciona ===== - Ampliar ''openapi.yaml'' a v1.0.0 — cambio **breaking**, por eso salta de versión mayor: ^ Operación ^ Petición ^ Respuestas ^ | Registro | ''POST /api/users'' · ''UserRequest'' | 201 · 400 · 409 | | Login | ''POST /api/auth/login'' · ''LoginRequest'' | 200 · 401 | | Refresh | ''POST /api/auth/refresh'' · ''RefreshToken'' | 200 · 401 | | Logout | ''POST /api/auth/logout'' · ''RefreshToken'' | 204 · 401 | | Mis pedidos | ''GET /api/me/orders'' | 200 · 401 | - Registro: ''name'', ''email'' y ''password'' (mínimo 8). Login y refresh devuelven ''TokenResponse'': ''accessToken'', ''refreshToken'' y ''expiresIn''. El logout revoca el refresh que recibe. - El cambio breaking: ''OrderRequest'' **pierde ''userId''** — la identidad del comprador ya no viaja en el cuerpo, sino en el token. Los pedidos de prueba de los hitos anteriores se limpian: su ''userId'' era provisional. ===== 2. El módulo users ===== * Módulo nuevo en la verificación, con la estructura aprendida en el [[proyecto:07-spring-jpa|hito 07]]: interfaces de repositorio en ''domain'' y mecanismo JPA en ''persistence''. * Tablas ''users'' y ''refresh_tokens'' de la [[proyecto:02-especificacion|especificación]]: ampliar el ''init.sql'' y reiniciar el volumen de MariaDB. La semilla trae un usuario ''ADMIN'' con su hash; el registro crea siempre ''USER''. * Contraseñas **hash**, nunca en claro. El dominio no conoce el mecanismo de hashing: define lo que necesita y la composición aporta la implementación, de la forma que decida cada quien; en el [[proyecto:09-spring-security|hito 09]] pasará a la maquinaria de **Spring Security** sin tocar el dominio. * El login devuelve el par de tokens: **access JWT** de vida corta, firmado en el servidor, y **refresh opaco** — una cadena aleatoria sin significado, persistida por su hash con su caducidad y su capacidad de revocación. El refresh se usa una vez: al usarlo se revoca y se entrega uno nuevo (**rotación**); el logout revoca el refresh recibido. Los claims del access token bastan para que el resto de la aplicación reconozca la identidad — id y rol — sin conocer nada del módulo users. El access token es **stateless**, pero el par no: el refresh rota al usarse y se revoca en el logout. La tabla ''refresh_tokens'' de la especificación existe por eso. ===== 3. Autenticación en el filtro ===== * Aún sin **Spring Security**, la protección es un filtro Servlet propio, como en el [[temario:06-login-jwt|tema 06]]: extrae el token de la cabecera, valida firma y caducidad, deja la identidad a la vista de los controladores y responde 401 en las rutas que la exigen. Los filtros no pasan por los manejadores de la presentación: el 401 lo escribe el filtro con el formato del contrato. * Las rutas protegidas son todavía una lista gestionada a mano en el filtro. * Con la identidad en marcha, ''GET /api/me/orders'' devuelve solo los pedidos del comprador: la identidad viene del token, no de un parámetro. La lista de rutas «a mano» está a propósito: muestra el límite del enfoque. En el [[proyecto:09-spring-security|hito 09]] la sustituye la cadena declarativa de **Spring Security**, sin cambiar el flujo de tokens. ===== 4. Pruebas ===== * **Registro**: usuario correcto → 201; email repetido → 409; contraseña corta → 400. * **Login**: credenciales correctas → 200 con el par de tokens; usuario o contraseña incorrectos → 401. * **Refresh**: correcto → par nuevo y el viejo revocado; reusar el viejo → 401; refresh caducado → 401. * **Logout**: revoca el refresh; usarlo después → 401. * **Filtro**: ruta protegida sin token → 401; con token caducado o mal firmado → 401; rutas públicas sin token pasan; la identidad llega al controlador con id y rol. * **Mis pedidos**: devuelve solo los del comprador del token. ===== Comprobación ===== * El flujo completo corre de punta a punta: registro → login → pedido → mis pedidos → cancelación → refresh → logout. * El contrato está en v1.0.0 y el Swagger refleja las operaciones nuevas; ''OrderRequest'' ya no lleva ''userId''. * La verificación de límites sigue verde con el tercer módulo. * Commit y tag ''hito-08''. **Siguiente hito:** [[proyecto:09-spring-security|hito 09 — Integración con Spring Security]].