Alejandro Lara 59bd2d2d8a Integrar autenticación Cognito con Floci local y soporte AWS
- Añade servicios y configuración Cognito para login web (admins) y API (scanners)
- Soporta Floci cuando AWS_ENDPOINT_URL está definido; AWS real en caso contrario
- Incluye bootstrap Floci, docker-compose.local.yml y carga automática en run-local.sh
- Documenta arquitectura, flujos y arranque manual/IntelliJ en README
- Actualiza gitignore: excluye Flutter, H2 local y artefactos generados
2026-06-09 16:59:50 -06:00

Base Admin Web

Proyecto base de Spring Boot con Metronic, autenticación Cognito (o local), panel administrativo y API de escaneo para la app móvil Flutter.

Sirve como plantilla reutilizable para nuevos paneles internos: estructura modular, seguridad dual (web + API), módulo de códigos pase con QR y soporte H2/PostgreSQL.


Tabla de contenidos


Vista general

El sistema tiene dos canales de acceso con roles distintos:

Canal Usuario Rol Autenticación
Panel web (Thymeleaf) admin ROLE_ADMIN Sesión HTTP (form login)
API móvil / externa scanner ROLE_SCANNER JWT Bearer (stateless)

Por defecto ambos canales usan Amazon Cognito (emulado en local con Floci). Con APP_COGNITO_ENABLED=false se usa autenticación local contra la base de datos y JWT firmado con secreto propio.

flowchart TB
    subgraph clients["Clientes"]
        Browser["Navegador<br/>Panel admin"]
        Flutter["App Flutter<br/>QR Scanner"]
    end

    subgraph app["Spring Boot — base-admin-web-jar"]
        WebChain["Filter chain web<br/>sesión + CSRF"]
        ApiChain["Filter chain API<br/>stateless + JWT"]
        PassCodes["PassCodeService"]
        DB[("H2 / PostgreSQL")]
    end

    subgraph auth["Autenticación"]
        Cognito["Cognito<br/>Floci local o AWS"]
        LocalAuth["BD local + JWT HS256<br/>APP_COGNITO_ENABLED=false"]
    end

    Browser -->|"/login, /admin/**"| WebChain
    Flutter -->|"/api/auth/login<br/>/api/pass-codes/scan"| ApiChain
    WebChain --> PassCodes
    ApiChain --> PassCodes
    PassCodes --> DB
    WebChain --> Cognito
    ApiChain --> Cognito
    WebChain -.-> LocalAuth
    ApiChain -.-> LocalAuth

URLs locales:

Recurso URL
Login http://localhost:8080/login
Dashboard http://localhost:8080/dashboard
Códigos pase http://localhost:8080/admin/pass-codes
H2 Console http://localhost:8080/h2-console
Floci (Cognito local) http://localhost:4566

Stack

Capa Tecnología
Backend Spring Boot 4.0.6, Java 25
Seguridad Spring Security, OAuth2 Resource Server, AWS SDK Cognito
Vistas Thymeleaf + Metronic (base-web)
Persistencia JPA / Hibernate, H2 (dev), PostgreSQL (perfil postgres)
Auth Cognito (default) o BD local + JWT HS256
Móvil Flutter (flutter-app/qrscanner)
Local AWS Floci (Cognito en puerto 4566)

Arquitectura del sistema

Capas y dependencias

flowchart BT
    jar["base-admin-web-jar<br/>punto de entrada + application.yml"]
    web["base-admin-web-web<br/>controladores + Thymeleaf"]
    config["base-admin-web-configuration<br/>seguridad, JWT, Cognito"]
    app["base-admin-web-application<br/>servicios + carga inicial"]
    domain["base-admin-web-domain<br/>entidades + repositorios"]

    jar --> web
    jar --> config
    jar --> app
    jar --> domain
    web --> config
    web --> app
    config --> app
    app --> domain
Módulo Responsabilidad
base-admin-web-domain Entidades JPA (AppUser, PassCode, Role), repositorios
base-admin-web-application PassCodeService, AppUserDetailsService, InitialDataLoader
base-admin-web-configuration SecurityConfig, ApiSecurityConfig, Cognito, JwtConfig
base-admin-web-web Controladores MVC y API, DTOs, plantillas HTML
base-admin-web-jar BaseAdminWebApplication, perfiles, dependencias runtime (H2, PostgreSQL)

Dos cadenas de seguridad

Spring Security define dos filter chains con distinto comportamiento:

flowchart LR
    subgraph order1["@Order(1) — ApiSecurityConfig"]
        A1["/api/**"]
        A2["Stateless"]
        A3["JWT Bearer"]
        A4["ROLE_SCANNER en /scan"]
    end

    subgraph order2["@Order(2) — SecurityConfig"]
        B1["Todo excepto /api/**"]
        B2["Sesión HTTP"]
        B3["Form login /login"]
        B4["ROLE_ADMIN en /admin/**"]
    end

    A1 --> A2 --> A3 --> A4
    B1 --> B2 --> B3 --> B4
Ruta Acceso Mecanismo
/login, /assets/**, /error, /h2-console/** Público
/dashboard, /admin/** ROLE_ADMIN Sesión web
/api/auth/login, /api/auth/refresh Público Emite tokens (solo scanner)
/api/pass-codes/scan ROLE_SCANNER JWT Bearer

Seguridad y autenticación

Usuarios iniciales

Administrador (panel web)

  • Usuario: admin / Admin123*
  • Grupo Cognito: adminsROLE_ADMIN
  • Acceso: login en /login, dashboard, módulo de códigos pase
  • El usuario scanner no puede entrar al panel web

Escáner (API / Flutter)

  • Usuario: scanner / Scanner123*
  • Grupo Cognito: scannersROLE_SCANNER
  • Acceso: POST /api/auth/login y POST /api/pass-codes/scan
  • El usuario admin no puede autenticarse por API

Flujo de login web (panel admin)

sequenceDiagram
    actor Admin as Navegador (admin)
    participant SC as SecurityConfig
    participant CP as CognitoWebAuthenticationProvider
    participant CA as CognitoUserAuthenticator
    participant Cognito as Cognito (Floci/AWS)

    Admin->>SC: POST /login (user, password)
    SC->>CP: authenticate()
    CP->>CA: admin client + ROLE_ADMIN
    CA->>Cognito: InitiateAuth USER_PASSWORD_AUTH
    Cognito-->>CA: tokens + cognito:groups
    CA-->>CP: AuthenticatedWebUser (grupo admins)
    CP-->>SC: sesión HTTP creada
    SC-->>Admin: redirect /dashboard

Clases clave: SecurityConfig, CognitoWebSecurityConfig, CognitoWebAuthenticationProvider, CognitoUserAuthenticator, AuthController

Con APP_COGNITO_ENABLED=false, el flujo usa DaoAuthenticationProviderAppUserDetailsService → tabla users (BCrypt).

Flujo de login y escaneo API

sequenceDiagram
    actor App as Flutter (scanner)
    participant API as AuthApiController
    participant AS as ApiAuthService
    participant Cognito as Cognito
    participant Scan as PassCodeApiController
    participant Svc as PassCodeService

    App->>API: POST /api/auth/login
    API->>AS: login()
    AS->>Cognito: scanner client + ROLE_SCANNER
    Cognito-->>App: accessToken, refreshToken, idToken

    App->>Scan: POST /api/pass-codes/scan<br/>Authorization: Bearer ...
    Scan->>Scan: validar JWT (JWK o HS256)
    Scan->>Svc: scan(code)
    Svc-->>App: código marcado UTILIZADO

Clases clave: ApiSecurityConfig, AuthApiController, ApiAuthService, CognitoAuthService, PassCodeApiController, JwtConfig

Modo local sin Cognito

Con APP_COGNITO_ENABLED=false (perfil test lo usa automáticamente):

flowchart LR
    subgraph web["Web"]
        W1["Form login"] --> W2["AppUser en BD"]
    end
    subgraph api["API"]
        A1["POST /api/auth/login"] --> A2["BD + JwtEncoder HS256"]
        A3["POST /api/auth/refresh"] --> A4["No disponible"]
        A5["Bearer token"] --> A6["localJwtDecoder HS256"]
    end
APP_COGNITO_ENABLED Web API login Validación JWT Refresh
true (default) Cognito client admin Cognito client scanner JWK Set URI
false BD + BCrypt BD + JWT local Secreto HS256 No

Los usuarios se siembran con InitialDataLoader usando APP_ADMIN_* y APP_SCANNER_*.


Cognito: local (Floci) vs AWS

El SDK de Cognito elige el destino según AWS_ENDPOINT_URL:

flowchart TD
    Start["APP_COGNITO_ENABLED=true"]
    Check{"¿AWS_ENDPOINT_URL<br/>definido?"}
    Floci["Floci localhost:4566<br/>credenciales test/test"]
    AWS["Cognito real en AWS<br/>DefaultCredentialsProvider<br/>IAM / perfil / env AWS"]

    Start --> Check
    Check -->|Sí| Floci
    Check -->|No| AWS

Local con Floci

./run-local.sh hace todo el setup:

  1. Levanta Floci (y PostgreSQL si el perfil lo incluye)
  2. Espera el bootstrap de Cognito (hasta 120 s)
  3. Carga docker/floci/generated/cognito.local.env automáticamente

El script docker/floci/init/ready.d/10-cognito-bootstrap.sh crea:

Recurso Valor
User Pool ID us-east-1_BaseAdminWeb
Usuario admin admin / Admin123* → grupo admins
Usuario scanner scanner / Scanner123* → grupo scanners
App client admin base-admin-web-admin
App client scanner base-admin-web-scanner

Variables generadas en docker/floci/generated/cognito.local.env (gitignored).

Levantar solo Floci:

./scripts/floci-up.sh

Levantar stack Docker completo:

docker compose -f docker-compose.local.yml up -d
Servicio Puerto Persistencia
Floci 4566 volumen base_admin_web_floci_data
PostgreSQL 5432 volumen base_admin_web_postgres_data

Producción con AWS Cognito

No definir AWS_ENDPOINT_URL. Configura el User Pool real:

export APP_COGNITO_ENABLED=true
export COGNITO_REGION=us-east-1
export COGNITO_USER_POOL_ID=us-east-1_XXXXX
export COGNITO_ISSUER_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX
export COGNITO_JWK_SET_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX/.well-known/jwks.json
export COGNITO_ADMIN_CLIENT_ID=...
export COGNITO_SCANNER_CLIENT_ID=...
# Credenciales AWS vía IAM role, perfil o AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY

En AWS replica la misma estructura que Floci: grupos admins/scanners, clients con USER_PASSWORD_AUTH habilitado.

Verificar Cognito local

export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test

aws cognito-idp list-user-pools --max-results 10 --endpoint-url $AWS_ENDPOINT_URL
aws cognito-idp list-users --user-pool-id us-east-1_BaseAdminWeb --endpoint-url $AWS_ENDPOINT_URL

Módulo de códigos pase

Panel para dar de alta y consultar códigos pase con QR.

stateDiagram-v2
    [*] --> ACTIVO: admin crea código (10 chars)
    ACTIVO --> UTILIZADO: scanner escanea QR
Campo Descripción
Nombre Etiqueta descriptiva
Código Aleatorio de 10 caracteres (único)
Estatus ACTIVO / UTILIZADO
Escaneado Indicador booleano
Fechas Creación y escaneo

Panel web: GET /admin/pass-codes — tabla Metronic, modal QR, refresco automático al cerrar el modal.

Entidad: PassCodeServicio: PassCodeServiceControladores: PassCodeController (web), PassCodeApiController (API).


API REST

Login escáner

POST /api/auth/login
Content-Type: application/json

{
  "username": "scanner",
  "password": "Scanner123*"
}

Respuesta (Cognito):

{
  "success": true,
  "accessToken": "eyJhbG...",
  "refreshToken": "dXMtZWFzdC0x...",
  "idToken": "eyJhbG...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "username": "scanner",
  "roles": ["ROLE_SCANNER"]
}

Renovar token

POST /api/auth/refresh
Content-Type: application/json

{
  "refreshToken": "dXMtZWFzdC0x..."
}

Solo disponible con Cognito habilitado.

Escanear código pase

POST /api/pass-codes/scan
Authorization: Bearer eyJhbG...
Content-Type: application/json

{
  "code": "K7P2M9XQ4R"
}

Listado JSON (panel admin, requiere sesión)

GET /admin/pass-codes/list

App Flutter (escáner)

Ubicación: flutter-app/qrscanner/

flowchart LR
    Login["LoginScreen"] --> Home["HomeScreen"]
    Home --> Scanner["ScannerScreen"]
    Scanner -->|QR detectado| API["POST /api/pass-codes/scan"]
    Login -->|credenciales| Auth["POST /api/auth/login"]
Archivo Rol
lib/screens/login_screen.dart Login y sesión
lib/screens/scanner_screen.dart Lectura QR + escaneo
lib/services/auth_service.dart Login, SharedPreferences
lib/services/pass_code_service.dart Scan con Bearer token
lib/config/api_config.dart URL base de la API

Ejecutar apuntando al backend local:

cd flutter-app/qrscanner
flutter run --dart-define=API_BASE_URL=http://TU_IP:8080

La app valida que el usuario tenga ROLE_SCANNER (o grupo scanners) antes de permitir el escaneo.


Arranque local

Opción recomendada

./run-local.sh

El script:

  • Detecta Java 25 (Temurin)
  • Levanta Floci (y PostgreSQL si aplica)
  • Espera y carga cognito.local.env
  • Compila e inicia base-admin-web-jar

Si ya tienes AWS_ENDPOINT_URL exportado, no sobrescribe tus variables (útil para probar contra AWS).

Maven manual (terminal)

# 1. Levantar Floci (Cognito local)
docker compose -f docker-compose.local.yml up -d floci

# 2. Esperar el bootstrap (genera cognito.local.env)
#    Revisa que exista: docker/floci/generated/cognito.local.env

# 3. Cargar variables Cognito en la shell
set -a && source docker/floci/generated/cognito.local.env && set +a

# 4. Compilar y arrancar
mvn -pl base-admin-web-jar -am -DskipTests install
mvn -pl base-admin-web-jar spring-boot:run

Si Floci ya estaba levantado de antes, basta con el source y el spring-boot:run.

PostgreSQL local

SPRING_PROFILES_ACTIVE=postgres ./run-local.sh

PostgreSQL remoto

SPRING_PROFILES_ACTIVE=postgres \
SPRING_DATASOURCE_URL=jdbc:postgresql://TU_SERVIDOR:5432/base_admin_web \
SPRING_DATASOURCE_USERNAME=tu_usuario \
SPRING_DATASOURCE_PASSWORD=tu_password \
./run-local.sh

Arranque manual e IntelliJ IDEA

Si abres el proyecto en IntelliJ (o cualquier IDE) sin usar run-local.sh, debes levantar Floci y configurar variables de entorno tú mismo. run-local.sh automatiza exactamente esos pasos.

flowchart TD
    A["Abrir proyecto Maven en IntelliJ"] --> B["Java 25 como Project SDK"]
    B --> C["docker compose up floci"]
    C --> D["Esperar cognito.local.env"]
    D --> E["Configurar Run Configuration"]
    E --> F["Run BaseAdminWebApplication"]
    F --> G["http://localhost:8080/login"]

Requisitos previos

Requisito Versión / nota
JDK Java 25 (Temurin recomendado)
Maven Incluido en el proyecto (wrapper no obligatorio; usa el Maven del IDE)
Docker Para Cognito local (Floci) en puerto 4566
IntelliJ Ultimate o Community (con plugin Spring Boot en Community)

1. Importar el proyecto

  1. File → Open y selecciona la carpeta raíz del repo (donde está el pom.xml padre).
  2. IntelliJ detecta el proyecto Maven multi-módulo. Espera a que termine la indexación e importación.
  3. File → Project Structure → Project: SDK = Java 25, language level = 25.
  4. File → Settings → Build → Build Tools → Maven → Runner: JRE = Project SDK (Java 25).

2. Levantar Floci (Cognito local)

En una terminal (puede ser la Terminal integrada de IntelliJ):

docker compose -f docker-compose.local.yml up -d floci

La primera vez Floci ejecuta el bootstrap y escribe:

docker/floci/generated/cognito.local.env

Comprueba que el archivo existe (puede tardar ~30120 s):

cat docker/floci/generated/cognito.local.env

Si no aparece, revisa los logs:

docker logs base-admin-web-floci

PostgreSQL local: si usas el perfil postgres, levanta también el servicio:

docker compose -f docker-compose.local.yml up -d postgres floci

3. Run Configuration en IntelliJ

Crea una configuración Spring Boot (o Application):

Campo Valor
Name BaseAdminWebApplication
Main class mx.gob.slp.baseadminweb.jar.BaseAdminWebApplication
Module base-admin-web-jar
Working directory $PROJECT_DIR$/base-admin-web-jar
Active profiles dev (o postgres si aplica)

Working directory: H2 guarda datos en ./data/h2/ relativo al directorio de trabajo. Debe ser base-admin-web-jar para que coincida con mvn spring-boot:run y no mezclar rutas de BD.

Variables de entorno (Cognito local)

En Environment variables, pega el contenido de docker/floci/generated/cognito.local.env:

COGNITO_USER_POOL_ID=us-east-1_BaseAdminWeb
COGNITO_REGION=us-east-1
COGNITO_ISSUER_URI=http://localhost:4566/us-east-1_BaseAdminWeb
COGNITO_JWK_SET_URI=http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json
COGNITO_ADMIN_CLIENT_ID=<valor del archivo generado>
COGNITO_SCANNER_CLIENT_ID=<valor del archivo generado>
COGNITO_ADMIN_GROUP=admins
COGNITO_SCANNER_GROUP=scanners
AWS_ENDPOINT_URL=http://localhost:4566
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test

Los CLIENT_ID cambian en cada bootstrap; copia siempre los del archivo generado, no uses valores fijos de ejemplos antiguos.

Alternativa: plugin EnvFile → añade docker/floci/generated/cognito.local.env como archivo de entorno en la Run Configuration.

Perfil postgres en IntelliJ

Además de Active profiles = postgres, añade en Environment variables:

SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/base_admin_web
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=postgres

(Y las variables Cognito del paso anterior.)

4. Ejecutar

  1. Build → Build Project (o Ctrl+F9).
  2. Run ▶ en BaseAdminWebApplication.
  3. Abre http://localhost:8080/login — usuario admin / Admin123*.

5. Desarrollo sin Docker (modo local puro)

Si no quieres Floci, desactiva Cognito y usa usuarios de la BD:

Variable Valor
APP_COGNITO_ENABLED false

No hace falta AWS_ENDPOINT_URL ni levantar Docker. Los usuarios admin y scanner se crean al arrancar vía InitialDataLoader.

El login API devuelve JWT local (HS256); refresh no está disponible en este modo.

6. Checklist de problemas frecuentes

Síntoma Causa probable Solución
Login web falla con Cognito habilitado Floci no está arriba o faltan env vars docker compose up -d floci + cargar cognito.local.env
Login va a AWS real en local Falta AWS_ENDPOINT_URL Definir AWS_ENDPOINT_URL=http://localhost:4566
Invalid client id Client IDs desactualizados Regenerar con bootstrap y copiar de cognito.local.env
H2 vacío o ruta rara Working directory incorrecto $PROJECT_DIR$/base-admin-web-jar
Error de versión Java SDK distinto a 25 Project Structure → SDK Java 25
Tests en IDE fallan por Cognito Perfil test desactiva Cognito Ejecutar tests con perfil test (ya configurado en application-test.yml)

7. Comandos útiles desde IntelliJ

Acción Comando Maven (ventana Maven o terminal)
Compilar todo mvn -pl base-admin-web-jar -am -DskipTests install
Tests mvn test
Solo módulo jar clic derecho en base-admin-web-jar → Run Maven → spring-boot:run (tras exportar env vars en la shell)

Perfiles Spring

Perfil Base de datos Cognito Uso
dev (default) H2 en archivo ./data/h2/ Floci (manual o run-local.sh) Desarrollo diario
postgres PostgreSQL Floci Desarrollo con BD real
prod Configurable AWS Producción
test H2 en memoria create-drop Deshabilitado mvn test

Variables de entorno

Aplicación

Variable Default Descripción
SERVER_PORT 8080 Puerto HTTP
SPRING_PROFILES_ACTIVE dev Perfil Spring
APP_TITLE Base Admin Web Título en vistas

Cognito

Variable Local (Floci) Producción (AWS)
APP_COGNITO_ENABLED true true
COGNITO_REGION us-east-1 tu región
COGNITO_USER_POOL_ID us-east-1_BaseAdminWeb us-east-1_XXXXX
COGNITO_ISSUER_URI http://localhost:4566/... https://cognito-idp.{region}.amazonaws.com/{poolId}
COGNITO_JWK_SET_URI http://localhost:4566/.../jwks.json https://cognito-idp.{region}.amazonaws.com/{poolId}/.well-known/jwks.json
COGNITO_ADMIN_CLIENT_ID generado por Floci client del pool
COGNITO_SCANNER_CLIENT_ID generado por Floci client del pool
COGNITO_ADMIN_GROUP admins admins
COGNITO_SCANNER_GROUP scanners scanners
AWS_ENDPOINT_URL http://localhost:4566 no definir

Base de datos

Variable Descripción
SPRING_DATASOURCE_URL JDBC URL
SPRING_DATASOURCE_USERNAME Usuario BD
SPRING_DATASOURCE_PASSWORD Contraseña BD
SPRING_JPA_HIBERNATE_DDL_AUTO update por defecto

JWT local (solo APP_COGNITO_ENABLED=false)

Variable Descripción
APP_JWT_SECRET Secreto HS256 (mín. 32 caracteres)
APP_JWT_EXPIRATION_HOURS Expiración del token

Usuarios semilla (modo local sin Cognito)

Variable Default
APP_ADMIN_USERNAME / APP_ADMIN_PASSWORD admin / Admin123*
APP_SCANNER_USERNAME / APP_SCANNER_PASSWORD scanner / Scanner123*

Objetivo de esta base

Esta base deja resuelto lo mínimo para arrancar nuevos proyectos internos:

  • Autenticación Cognito (local y AWS) con fallback a BD local
  • Panel web con Metronic y sesión HTTP para administradores
  • API stateless con JWT para escaneo móvil o externo
  • App Flutter de referencia para escaneo QR
  • Módulo de códigos pase con alta, listado, QR y API de escaneo
  • Estructura modular Maven limpia
  • Soporte rápido para H2 y PostgreSQL
  • Floci para desarrollo sin depender de AWS

El dashboard se mantiene deliberadamente sencillo como punto de partida para futuras integraciones.

S
Description
No description provided
Readme 41 MiB
Languages
JavaScript 85.5%
CSS 14%
Java 0.3%
HTML 0.1%