Skip to content

Repository files navigation

Spring Boot Crash Course — API REST con Kotlin

Backend educativo construido con Spring Boot y Kotlin, que expone una API REST protegida con Spring Security y JWT (access + refresh), persistencia en MongoDB y validación de entrada con Bean Validation.


Descripción

Este proyecto implementa un flujo de autenticación basado en tokens: al iniciar sesión se emiten un access token (corta duración) y un refresh token (larga duración). El refresh se almacena de forma hasheada en base de datos y puede rotarse al renovar la sesión. Las rutas de negocio (por ejemplo, notas) exigen un access token válido en la cabecera Authorization.

Está pensado para servir como referencia clara para reclutadores o desarrolladores que se incorporan al código: capas separadas (controladores, seguridad, persistencia), configuración mínima por variables de entorno y contratos HTTP explícitos.


Tecnologías utilizadas

Área Tecnología
Lenguaje Kotlin
Framework Spring Boot 4.x
API Spring Web (REST)
Seguridad Spring Security (sesión stateless, filtro JWT)
Tokens JJWT (access / refresh con claim type)
Base de datos MongoDB (Spring Data MongoDB)
Validación spring-boot-starter-validation
Build Gradle (Kotlin DSL)
JVM Java 17

Arquitectura del proyecto

Se organiza en capas alineadas con una arquitectura limpia ligera:

  • Presentación: controladores REST (controllers) — DTOs de request/response y mapeo HTTP.
  • Aplicación / dominio de seguridad: servicios (security) — registro, login, refresh, generación y validación de JWT.
  • Infraestructura: repositorios y documentos MongoDB (database), filtro HTTP JWT, configuración de seguridad.

Las dependencias apuntan hacia adentro: los controladores delegan en servicios; la persistencia no expone detalles de Mongo a la capa HTTP.

flowchart LR
  Client[Cliente HTTP] --> Controllers[Controllers]
  Controllers --> AuthService[AuthService]
  Controllers --> Repos[Repositories]
  AuthService --> JwtService[JwtService]
  AuthService --> Repos
  JwtAuthFilter[JwtAuthFilter] --> JwtService
  Controllers --> JwtAuthFilter
  Repos --> MongoDB[(MongoDB)]
Loading

Instalación y configuración paso a paso

Requisitos previos

  • JDK 17 (coincide con el toolchain del proyecto).
  • MongoDB accesible (local, Docker o Atlas).
  • Gradle (el proyecto incluye Gradle Wrapper; no es obligatorio tener Gradle instalado globalmente).

1. Clonar el repositorio

git clone <URL_DE_TU_REPO>
cd spring_boot_crash_course

2. Configurar variables de entorno

Crea un archivo .env, exporta variables en tu shell o configura tu IDE con al menos:

  • MONGODB_CONNECTION_STRING
  • JWT_SECRET_BASE64

(Los nombres coinciden con application.properties.)

3. Verificar la cadena de conexión MongoDB

Ejemplo local típico:

mongodb://localhost:27017/YOUR_DB_NAME

Para MongoDB Atlas, usa la URI que proporciona el panel (usuario, contraseña y cluster).

4. Generar el secreto JWT en Base64

El código decodifica jwt.secret desde Base64 y lo usa como clave HMAC-SHA256. Debes usar una cadena Base64 que, al decodificar, tenga suficiente entropía (por ejemplo 32 bytes aleatorios codificados en Base64).

Ejemplo (solo para desarrollo; en producción usa un gestor de secretos):

openssl rand -base64 32

Asigna el resultado a JWT_SECRET_BASE64.


Variables de entorno necesarias

Variable Descripción
MONGODB_CONNECTION_STRING URI de conexión a MongoDB (incluye base de datos si aplica).
JWT_SECRET_BASE64 Secreto HMAC en Base64 (coherente con Keys.hmacShaKeyFor / HS256).

Opcionalmente puedes añadir en application.properties (o por entorno) otras propiedades estándar de Spring, por ejemplo server.port, si necesitas un puerto distinto del 8080 por defecto.


Cómo ejecutar el proyecto

Desde la raíz del repositorio:

./gradlew bootRun

Con variables en la misma línea (macOS / Linux):

export MONGODB_CONNECTION_STRING="mongodb://localhost:27017/YOUR_DB_NAME"
export JWT_SECRET_BASE64="$(openssl rand -base64 32)"
./gradlew bootRun

La API quedará disponible en http://localhost:8080 (salvo que cambies server.port).

Ejecutar tests:

./gradlew test

Endpoints principales

Base URL de ejemplo: http://localhost:8080.

Registro de usuario

POST /auth/registerpúblico

Request

{
  "email": "usuario@ejemplo.com",
  "password": "MiClaveSegura1"
}

Reglas de contraseña (validación): mínimo 9 caracteres, al menos una minúscula, una mayúscula y un dígito.

Response — cuerpo vacío con éxito (HTTP 200). Si el email ya existe, la aplicación puede responder con error según el manejo de excepciones configurado.


Login

POST /auth/loginpúblico

Request

{
  "email": "usuario@ejemplo.com",
  "password": "MiClaveSegura1"
}

Response (ejemplo)

{
  "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Credenciales incorrectas → 401 Unauthorized (según Spring Security / BadCredentialsException).


Refresh token

POST /auth/refreshpúblico (el cuerpo lleva el refresh, no la cabecera Bearer del access)

Request

{
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Response (ejemplo) — nuevo par de tokens (rotación del refresh almacenado):

{
  "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Refresh inválido, expirado o ya usado → 401 Unauthorized.


Endpoint protegido — listar notas del usuario

GET /notesrequiere autenticación

Headers

Authorization: Bearer <accessToken>

Response (ejemplo)

[
  {
    "id": "674a1b2c3d4e5f6789012345",
    "title": "Compras",
    "content": "Leche, pan",
    "color": 4294901760,
    "createdAt": "2026-03-31T12:00:00Z"
  }
]

Crear o actualizar nota

POST /notesrequiere autenticación

Headers

Authorization: Bearer <accessToken>
Content-Type: application/json

Request (nueva nota — sin id o con id opcional según tu flujo)

{
  "id": null,
  "title": "Ideas",
  "content": "API REST con JWT",
  "color": 4278190080
}

Response (ejemplo)

{
  "id": "674a1b2c3d4e5f6789012346",
  "title": "Ideas",
  "content": "API REST con JWT",
  "color": 4278190080,
  "createdAt": "2026-03-31T12:05:00Z"
}

Eliminar nota

DELETE /notes/{id}requiere autenticación (solo si la nota pertenece al usuario autenticado)

Headers

Authorization: Bearer <accessToken>

Response — sin cuerpo con éxito (según implementación del controlador).


Autenticación (JWT access y refresh)

Concepto Comportamiento en este proyecto
Access token Se envía en Authorization: Bearer <token>. Duración aproximada 15 minutos. Incluye claim type: "access". El filtro JwtAuthFilter valida el token y establece el principal como el ID de usuario (hex de ObjectId) en el contexto de seguridad.
Refresh token Duración larga (30 días en código). Claim type: "refresh". No va en rutas /notes para autorización; se usa solo contra POST /auth/refresh. En servidor se guarda un hash SHA-256 (Base64) del refresh en MongoDB; al usar el refresh se invalida el anterior y se emite uno nuevo (rotación).
Rutas públicas Todo bajo /auth/** está permitido sin Bearer. El resto exige un access token válido.

Flujo recomendado para clientes: guardar ambos tokens tras el login; cuando el access expire, llamar a /auth/refresh con el refresh; actualizar almacenamiento con el nuevo par.


Estructura de carpetas

spring_boot_crash_course/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle/
├── src/
│   ├── main/
│   │   ├── kotlin/com/jesushzc/spring_boot_crash_course/
│   │   │   ├── SpringBootCrashCourseApplication.kt
│   │   │   ├── GlobalValidationHandler.kt
│   │   │   ├── controllers/
│   │   │   │   ├── AuthController.kt
│   │   │   │   └── NoteController.kt
│   │   │   ├── security/
│   │   │   │   ├── SecurityConfig.kt
│   │   │   │   ├── JwtAuthFilter.kt
│   │   │   │   ├── JwtService.kt
│   │   │   │   ├── AuthService.kt
│   │   │   │   └── HashEncoder.kt
│   │   │   └── database/
│   │   │       ├── model/          # User, Note, RefreshToken
│   │   │       └── repository/     # Spring Data repositories
│   │   └── resources/
│   │       └── application.properties
│   └── test/
│       └── kotlin/...
└── README.md

Buenas prácticas implementadas

  • API stateless con Spring Security y sin sesión en servidor para la API REST.
  • Separación por capas (controladores, servicios de seguridad, persistencia).
  • Contraseñas nunca en claro en base de datos (hash con componente dedicado).
  • Refresh token rotativo y almacenado de forma hasheada (no se guarda el JWT en bruto).
  • Validación declarativa en DTOs de entrada (@Valid, @Email, @Pattern, @NotBlank).
  • JWT con tipo explícito (access / refresh) para reducir confusión entre tokens.
  • Configuración externa sensible vía variables de entorno (MONGODB_CONNECTION_STRING, JWT_SECRET_BASE64).

Posibles mejoras futuras

  • Documentación OpenAPI (SpringDoc / Swagger UI) generada desde los controladores.
  • Refresh token con detección de reutilización (logout global / revocación en cadena).
  • Rate limiting y política CORS explícita si hay front en otro origen.
  • Tests de integración para flujos /auth y /notes con @SpringBootTest y contenedor Testcontainers para MongoDB.
  • Refresh en cookie HttpOnly frente a solo cuerpo JSON, valorando riesgos XSS/CSRF.
  • Homogeneizar respuestas de error (códigos y cuerpos JSON) en un @ControllerAdvice centralizado.

Autor

Nombre / contacto: Jesus Clemente Hernandez / jclementeh07@gmail.com

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages