- 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
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
- Stack
- Arquitectura del sistema
- Módulos Maven
- Seguridad y autenticación
- Cognito: local (Floci) vs AWS
- Módulo de códigos pase
- API REST
- App Flutter (escáner)
- Arranque local
- Arranque manual e IntelliJ IDEA
- Perfiles Spring
- Variables de entorno
- Objetivo de esta base
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:
admins→ROLE_ADMIN - Acceso: login en
/login, dashboard, módulo de códigos pase - El usuario
scannerno puede entrar al panel web
Escáner (API / Flutter)
- Usuario:
scanner/Scanner123* - Grupo Cognito:
scanners→ROLE_SCANNER - Acceso:
POST /api/auth/loginyPOST /api/pass-codes/scan - El usuario
adminno 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 DaoAuthenticationProvider → AppUserDetailsService → 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 | Sí |
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:
- Levanta Floci (y PostgreSQL si el perfil lo incluye)
- Espera el bootstrap de Cognito (hasta 120 s)
- Carga
docker/floci/generated/cognito.local.envautomá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: PassCode → Servicio: PassCodeService → Controladores: 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
- File → Open y selecciona la carpeta raíz del repo (donde está el
pom.xmlpadre). - IntelliJ detecta el proyecto Maven multi-módulo. Espera a que termine la indexación e importación.
- File → Project Structure → Project: SDK = Java 25, language level = 25.
- 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 ~30–120 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 serbase-admin-web-jarpara que coincida conmvn spring-boot:runy 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
- Build → Build Project (o
Ctrl+F9). - Run ▶ en
BaseAdminWebApplication. - 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.