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
This commit is contained in:
Alejandro Lara
2026-06-09 16:59:50 -06:00
parent f8468350d5
commit 59bd2d2d8a
116 changed files with 1733 additions and 4318 deletions
@@ -1,6 +1,7 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.configuration.security.ApiAuthService;
import mx.gob.slp.baseadminweb.configuration.security.ApiLoginResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
@@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@@ -24,23 +26,46 @@ public class AuthApiController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
ApiAuthService.ApiLoginResponse response = apiAuthService.login(request.username(), request.password());
return ResponseEntity.ok(Map.of(
"success", true,
"accessToken", response.accessToken(),
"tokenType", response.tokenType(),
"expiresIn", response.expiresIn(),
"username", response.username(),
"roles", response.roles()
));
ApiLoginResponse response = apiAuthService.login(request.username(), request.password());
return ResponseEntity.ok(toAuthBody(response));
} catch (BadCredentialsException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
"success", false,
"message", ex.getMessage()
));
return unauthorized(ex.getMessage());
}
}
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody RefreshRequest request) {
try {
ApiLoginResponse response = apiAuthService.refresh(request.refreshToken());
return ResponseEntity.ok(toAuthBody(response));
} catch (BadCredentialsException ex) {
return unauthorized(ex.getMessage());
}
}
private Map<String, Object> toAuthBody(ApiLoginResponse response) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("success", true);
body.put("accessToken", response.accessToken());
body.put("refreshToken", response.refreshToken());
body.put("idToken", response.idToken());
body.put("tokenType", response.tokenType());
body.put("expiresIn", response.expiresIn());
body.put("username", response.username());
body.put("roles", response.roles());
return body;
}
private ResponseEntity<Map<String, Object>> unauthorized(String message) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
"success", false,
"message", message
));
}
public record LoginRequest(String username, String password) {
}
public record RefreshRequest(String refreshToken) {
}
}
@@ -1,7 +1,7 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import mx.gob.slp.baseadminweb.web.support.WebUserView;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,8 +15,8 @@ public class DashboardController {
}
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal AppUser user, Model model) {
model.addAttribute("user", user);
public String dashboard(Authentication authentication, Model model) {
model.addAttribute("user", WebUserView.from(authentication));
return "dashboard";
}
@@ -24,4 +24,4 @@ public class DashboardController {
public String adminHome() {
return "redirect:/dashboard";
}
}
}
@@ -1,9 +1,9 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.application.service.PassCodeService;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import mx.gob.slp.baseadminweb.web.dto.PassCodeListItem;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import mx.gob.slp.baseadminweb.web.support.WebUserView;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -26,8 +26,8 @@ public class PassCodeController {
}
@GetMapping
public String list(@AuthenticationPrincipal AppUser user, Model model) {
model.addAttribute("user", user);
public String list(Authentication authentication, Model model) {
model.addAttribute("user", WebUserView.from(authentication));
model.addAttribute("passCodes", passCodeService.findAll());
return "pass-codes";
}
@@ -0,0 +1,25 @@
package mx.gob.slp.baseadminweb.web.support;
import mx.gob.slp.baseadminweb.configuration.security.AuthenticatedWebUser;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import org.springframework.security.core.Authentication;
public record WebUserView(String username, String displayName) {
public static WebUserView from(Authentication authentication) {
if (authentication == null) {
return new WebUserView("usuario", "Usuario");
}
Object principal = authentication.getPrincipal();
if (principal instanceof AppUser appUser) {
return new WebUserView(appUser.getUsername(), appUser.getDisplayName());
}
if (principal instanceof AuthenticatedWebUser webUser) {
return new WebUserView(webUser.username(), webUser.displayName());
}
String name = authentication.getName();
return new WebUserView(name, name);
}
}
@@ -45,9 +45,12 @@
</div>
<div class="d-flex justify-content-between text-gray-500 fs-7 mb-8">
<span>Usuario demo: <strong>admin</strong></span>
<span>Usuario: <strong>admin</strong></span>
<span>Password: <strong>Admin123*</strong></span>
</div>
<div class="text-gray-500 fs-8 mb-8 text-center">
Autenticacion via Cognito local (grupo <strong>admins</strong>)
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">