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
+4
View File
@@ -27,6 +27,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cognitoidentityprovider</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
@@ -1,77 +1,50 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
@Service
public class ApiAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final ObjectProvider<CognitoAuthService> cognitoAuthService;
private final ObjectProvider<LocalJwtAuthService> localJwtAuthService;
private final AuthenticationManager authenticationManager;
private final JwtEncoder jwtEncoder;
private final JwtProperties jwtProperties;
public ApiAuthService(AuthenticationManager authenticationManager,
JwtEncoder jwtEncoder,
JwtProperties jwtProperties) {
this.authenticationManager = authenticationManager;
this.jwtEncoder = jwtEncoder;
this.jwtProperties = jwtProperties;
public ApiAuthService(ObjectProvider<CognitoAuthService> cognitoAuthService,
ObjectProvider<LocalJwtAuthService> localJwtAuthService) {
this.cognitoAuthService = cognitoAuthService;
this.localJwtAuthService = localJwtAuthService;
}
public ApiLoginResponse login(String username, String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList();
if (roles.stream().noneMatch(SCANNER_ROLE::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos.");
CognitoAuthService cognito = cognitoAuthService.getIfAvailable();
if (cognito != null) {
return cognito.login(username, password);
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L);
LocalJwtAuthService local = localJwtAuthService.getIfAvailable();
if (local != null) {
return local.login(username, password);
}
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(authentication.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim("roles", roles)
.build();
JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build();
String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue();
return new ApiLoginResponse(
accessToken,
"Bearer",
jwtProperties.getExpirationHours() * 3600L,
authentication.getName(),
roles
);
throw new IllegalStateException("No hay proveedor de autenticacion API configurado.");
}
public record ApiLoginResponse(
String accessToken,
String tokenType,
long expiresIn,
String username,
List<String> roles
) {
public ApiLoginResponse refresh(String refreshToken) {
if (refreshToken == null || refreshToken.isBlank()) {
throw new BadCredentialsException("El refresh token es obligatorio.");
}
CognitoAuthService cognito = cognitoAuthService.getIfAvailable();
if (cognito != null) {
return cognito.refresh(refreshToken);
}
LocalJwtAuthService local = localJwtAuthService.getIfAvailable();
if (local != null) {
return local.refresh(refreshToken);
}
throw new IllegalStateException("No hay proveedor de autenticacion API configurado.");
}
}
@@ -0,0 +1,14 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.util.List;
public record ApiLoginResponse(
String accessToken,
String refreshToken,
String idToken,
String tokenType,
long expiresIn,
String username,
List<String> roles
) {
}
@@ -27,7 +27,7 @@ public class ApiSecurityConfig {
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/auth/login", "/api/auth/refresh").permitAll()
.requestMatchers("/api/pass-codes/scan").hasRole("SCANNER")
.anyRequest().authenticated()
)
@@ -0,0 +1,6 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.io.Serializable;
public record AuthenticatedWebUser(String username, String displayName) implements Serializable {
}
@@ -0,0 +1,52 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final CognitoUserAuthenticator cognitoUserAuthenticator;
private final CognitoProperties cognitoProperties;
public CognitoAuthService(CognitoUserAuthenticator cognitoUserAuthenticator,
CognitoProperties cognitoProperties) {
this.cognitoUserAuthenticator = cognitoUserAuthenticator;
this.cognitoProperties = cognitoProperties;
}
public ApiLoginResponse login(String username, String password) {
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate(
username,
password,
cognitoProperties.getScannerClientId(),
SCANNER_ROLE
);
return toLoginResponse(result);
}
public ApiLoginResponse refresh(String refreshToken) {
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.refresh(
refreshToken,
cognitoProperties.getScannerClientId(),
SCANNER_ROLE
);
return toLoginResponse(result);
}
private ApiLoginResponse toLoginResponse(CognitoUserAuthenticator.AuthenticationResult result) {
var tokens = result.tokens();
return new ApiLoginResponse(
tokens.accessToken(),
result.refreshToken(),
tokens.idToken(),
tokens.tokenType() != null ? tokens.tokenType() : "Bearer",
tokens.expiresIn() != null ? tokens.expiresIn() : 0L,
result.username(),
result.roles()
);
}
}
@@ -0,0 +1,33 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import java.net.URI;
@Configuration
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoClientConfig {
@Bean(destroyMethod = "close")
CognitoIdentityProviderClient cognitoIdentityProviderClient(CognitoProperties properties) {
var builder = CognitoIdentityProviderClient.builder()
.region(Region.of(properties.getRegion()));
if (properties.usesLocalEndpoint()) {
builder.endpointOverride(URI.create(properties.getEndpoint()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("test", "test")));
} else {
builder.credentialsProvider(DefaultCredentialsProvider.create());
}
return builder.build();
}
}
@@ -0,0 +1,31 @@
package mx.gob.slp.baseadminweb.configuration.security;
import com.nimbusds.jwt.JWTParser;
final class CognitoDisplayNameResolver {
private CognitoDisplayNameResolver() {
}
static String resolve(String idToken, String accessToken, String fallbackUsername) {
String displayName = extractNameClaim(idToken);
if (displayName == null || displayName.isBlank()) {
displayName = extractNameClaim(accessToken);
}
if (displayName == null || displayName.isBlank()) {
return fallbackUsername;
}
return displayName;
}
private static String extractNameClaim(String token) {
if (token == null || token.isBlank()) {
return null;
}
try {
return JWTParser.parse(token).getJWTClaimsSet().getStringClaim("name");
} catch (Exception ex) {
return null;
}
}
}
@@ -0,0 +1,102 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app.cognito")
public class CognitoProperties {
private boolean enabled;
private String region = "us-east-1";
private String userPoolId = "us-east-1_BaseAdminWeb";
private String issuerUri = "http://localhost:4566/us-east-1_BaseAdminWeb";
private String jwkSetUri = "http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json";
private String endpoint;
private String scannerClientId;
private String adminClientId;
private String scannerGroup = "scanners";
private String adminGroup = "admins";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getUserPoolId() {
return userPoolId;
}
public void setUserPoolId(String userPoolId) {
this.userPoolId = userPoolId;
}
public String getIssuerUri() {
return issuerUri;
}
public void setIssuerUri(String issuerUri) {
this.issuerUri = issuerUri;
}
public String getJwkSetUri() {
return jwkSetUri;
}
public void setJwkSetUri(String jwkSetUri) {
this.jwkSetUri = jwkSetUri;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public boolean usesLocalEndpoint() {
return endpoint != null && !endpoint.isBlank();
}
public String getScannerClientId() {
return scannerClientId;
}
public void setScannerClientId(String scannerClientId) {
this.scannerClientId = scannerClientId;
}
public String getAdminClientId() {
return adminClientId;
}
public void setAdminClientId(String adminClientId) {
this.adminClientId = adminClientId;
}
public String getScannerGroup() {
return scannerGroup;
}
public void setScannerGroup(String scannerGroup) {
this.scannerGroup = scannerGroup;
}
public String getAdminGroup() {
return adminGroup;
}
public void setAdminGroup(String adminGroup) {
this.adminGroup = adminGroup;
}
}
@@ -0,0 +1,29 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
final class CognitoRoleMapper {
private CognitoRoleMapper() {
}
static List<String> toRoles(Collection<String> groups, CognitoProperties properties) {
if (groups == null || groups.isEmpty()) {
return List.of();
}
List<String> roles = new ArrayList<>();
for (String group : groups) {
if (properties.getScannerGroup().equalsIgnoreCase(group)) {
roles.add("ROLE_SCANNER");
} else if (properties.getAdminGroup().equalsIgnoreCase(group)) {
roles.add("ROLE_ADMIN");
} else {
roles.add("ROLE_" + group.toUpperCase());
}
}
return roles;
}
}
@@ -0,0 +1,119 @@
package mx.gob.slp.baseadminweb.configuration.security;
import com.nimbusds.jwt.JWTParser;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.NotAuthorizedException;
import software.amazon.awssdk.services.cognitoidentityprovider.model.UserNotFoundException;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
@Component
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoUserAuthenticator {
private final CognitoIdentityProviderClient cognitoClient;
private final CognitoProperties cognitoProperties;
public CognitoUserAuthenticator(CognitoIdentityProviderClient cognitoClient,
CognitoProperties cognitoProperties) {
this.cognitoClient = cognitoClient;
this.cognitoProperties = cognitoProperties;
}
public AuthenticationResult authenticate(String username,
String password,
String clientId,
String requiredRole) {
try {
InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder()
.authFlow(AuthFlowType.USER_PASSWORD_AUTH)
.clientId(clientId)
.authParameters(Map.of(
"USERNAME", username,
"PASSWORD", password
))
.build());
return toAuthenticationResult(response.authenticationResult(), username, requiredRole, null);
} catch (NotAuthorizedException | UserNotFoundException ex) {
throw new BadCredentialsException("Credenciales invalidas.");
}
}
public AuthenticationResult refresh(String refreshToken, String clientId, String requiredRole) {
try {
InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder()
.authFlow(AuthFlowType.REFRESH_TOKEN_AUTH)
.clientId(clientId)
.authParameters(Map.of("REFRESH_TOKEN", refreshToken))
.build());
return toAuthenticationResult(response.authenticationResult(), null, requiredRole, refreshToken);
} catch (NotAuthorizedException ex) {
throw new BadCredentialsException("Refresh token invalido o expirado.");
}
}
private AuthenticationResult toAuthenticationResult(AuthenticationResultType authResult,
String fallbackUsername,
String requiredRole,
String fallbackRefreshToken) {
if (authResult == null || authResult.accessToken() == null) {
throw new BadCredentialsException("No se pudo autenticar con Cognito.");
}
List<String> groups = extractGroups(authResult.accessToken());
List<String> roles = CognitoRoleMapper.toRoles(groups, cognitoProperties);
if (roles.stream().noneMatch(requiredRole::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para acceder.");
}
String username = extractUsername(authResult.accessToken(), fallbackUsername);
String refreshToken = authResult.refreshToken() != null
? authResult.refreshToken()
: fallbackRefreshToken;
return new AuthenticationResult(authResult, username, roles, groups, refreshToken);
}
private String extractUsername(String accessToken, String fallback) {
try {
var claims = JWTParser.parse(accessToken).getJWTClaimsSet();
String username = claims.getStringClaim("username");
if (username == null || username.isBlank()) {
username = claims.getStringClaim("cognito:username");
}
return username != null && !username.isBlank() ? username : fallback;
} catch (ParseException ex) {
throw new BadCredentialsException("No se pudo leer el usuario del token.");
}
}
private List<String> extractGroups(String accessToken) {
try {
var claims = JWTParser.parse(accessToken).getJWTClaimsSet();
List<String> groups = claims.getStringListClaim("cognito:groups");
return groups != null ? groups : List.of();
} catch (ParseException ex) {
throw new BadCredentialsException("No se pudieron leer los grupos del token.");
}
}
public record AuthenticationResult(
AuthenticationResultType tokens,
String username,
List<String> roles,
List<String> groups,
String refreshToken
) {
}
}
@@ -0,0 +1,63 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
@Component
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoWebAuthenticationProvider implements AuthenticationProvider {
private static final String ADMIN_ROLE = "ROLE_ADMIN";
private final CognitoUserAuthenticator cognitoUserAuthenticator;
private final CognitoProperties cognitoProperties;
public CognitoWebAuthenticationProvider(CognitoUserAuthenticator cognitoUserAuthenticator,
CognitoProperties cognitoProperties) {
this.cognitoUserAuthenticator = cognitoUserAuthenticator;
this.cognitoProperties = cognitoProperties;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
Object credentials = authentication.getCredentials();
if (credentials == null) {
throw new BadCredentialsException("La contrasena es obligatoria.");
}
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate(
username,
credentials.toString(),
cognitoProperties.getAdminClientId(),
ADMIN_ROLE
);
var authorities = result.roles().stream()
.map(SimpleGrantedAuthority::new)
.toList();
String displayName = CognitoDisplayNameResolver.resolve(
result.tokens().idToken(),
result.tokens().accessToken(),
result.username()
);
return UsernamePasswordAuthenticationToken.authenticated(
new AuthenticatedWebUser(result.username(), displayName),
null,
authorities
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@@ -0,0 +1,17 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
@Configuration
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoWebSecurityConfig {
@Bean(name = "webAuthenticationManager")
AuthenticationManager webAuthenticationManager(CognitoWebAuthenticationProvider cognitoWebAuthenticationProvider) {
return new ProviderManager(cognitoWebAuthenticationProvider);
}
}
@@ -1,9 +1,13 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
@@ -19,12 +23,16 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
@EnableConfigurationProperties({JwtProperties.class, CognitoProperties.class})
public class JwtConfig {
@Bean
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
JwtEncoder jwtEncoder(JwtProperties jwtProperties) {
OctetSequenceKey jwk = new OctetSequenceKey.Builder(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8))
.algorithm(JWSAlgorithm.HS256)
@@ -33,20 +41,35 @@ public class JwtConfig {
}
@Bean
JwtDecoder jwtDecoder(JwtProperties jwtProperties) {
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
JwtDecoder localJwtDecoder(JwtProperties jwtProperties) {
return NimbusJwtDecoder.withSecretKey(secretKey(jwtProperties))
.macAlgorithm(MacAlgorithm.HS256)
.build();
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthoritiesClaimName("roles");
authoritiesConverter.setAuthorityPrefix("");
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
JwtDecoder cognitoJwtDecoder(CognitoProperties cognitoProperties) {
return NimbusJwtDecoder.withJwkSetUri(cognitoProperties.getJwkSetUri()).build();
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter(CognitoProperties cognitoProperties) {
JwtGrantedAuthoritiesConverter localRolesConverter = new JwtGrantedAuthoritiesConverter();
localRolesConverter.setAuthoritiesClaimName("roles");
localRolesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<String> groups = jwt.getClaimAsStringList("cognito:groups");
CognitoRoleMapper.toRoles(groups, cognitoProperties).stream()
.map(SimpleGrantedAuthority::new)
.forEach(authorities::add);
authorities.addAll(localRolesConverter.convert(jwt));
return authorities;
});
return authenticationConverter;
}
@@ -0,0 +1,76 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
@Service
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
public class LocalJwtAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final AuthenticationManager authenticationManager;
private final JwtEncoder jwtEncoder;
private final JwtProperties jwtProperties;
public LocalJwtAuthService(AuthenticationManager authenticationManager,
JwtEncoder jwtEncoder,
JwtProperties jwtProperties) {
this.authenticationManager = authenticationManager;
this.jwtEncoder = jwtEncoder;
this.jwtProperties = jwtProperties;
}
public ApiLoginResponse login(String username, String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList();
if (roles.stream().noneMatch(SCANNER_ROLE::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos.");
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L);
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(authentication.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim("roles", roles)
.build();
JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build();
String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue();
return new ApiLoginResponse(
accessToken,
null,
null,
"Bearer",
jwtProperties.getExpirationHours() * 3600L,
authentication.getName(),
roles
);
}
public ApiLoginResponse refresh(String refreshToken) {
throw new BadCredentialsException("Refresh token no disponible sin Cognito.");
}
}
@@ -1,10 +1,13 @@
package mx.gob.slp.baseadminweb.configuration.security;
import mx.gob.slp.baseadminweb.application.service.AppUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -22,12 +25,17 @@ public class SecurityConfig {
private final AppUserDetailsService appUserDetailsService;
@Autowired(required = false)
@Qualifier("webAuthenticationManager")
private AuthenticationManager webAuthenticationManager;
public SecurityConfig(AppUserDetailsService appUserDetailsService) {
this.appUserDetailsService = appUserDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain securityFilterChain(HttpSecurity http,
PasswordEncoder passwordEncoder) throws Exception {
http
.securityMatcher(request -> !request.getRequestURI().startsWith("/api/"))
.authorizeHttpRequests(authorize -> authorize
@@ -49,9 +57,16 @@ public class SecurityConfig {
.deleteCookies("JSESSIONID")
)
.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
.userDetailsService(appUserDetailsService)
.rememberMe(Customizer.withDefaults());
if (webAuthenticationManager != null) {
http.authenticationManager(webAuthenticationManager);
} else {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(appUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(daoAuthenticationProvider);
}
http.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**"));
return http.build();
}
@@ -65,4 +80,4 @@ public class SecurityConfig {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
}