spring-bootkotlinawscognitojwtseguridadgradle
En este tutorial vas a configurar Spring Boot para validar tokens JWT de AWS Cognito y proteger tus endpoints. Nos enfocamos únicamente en la validación: el inicio de sesión lo gestiona el frontend o una aplicación externa. Tu backend es stateless y solo se encarga de verificar que el token sea legítimo.
Esta configuración es ideal cuando:
- Tu frontend gestiona el login con Cognito directamente
- Tienes una app móvil que ya obtiene tokens de Cognito
- Quieres un backend stateless que solo valide y proteja endpoints
Dependencias
En tu
build.gradle.kts:kotlin
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.security:spring-security-oauth2-resource-server")
implementation("org.springframework.security:spring-security-oauth2-jose")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
}¿Qué hace cada dependencia?
spring-security-oauth2-resource-server: convierte tu app en un Resource Server que valida tokensspring-security-oauth2-jose: maneja la verificación criptográfica de JWT (firma RS256, claves públicas)
Configurar Cognito
En
application.yml, apunta al issuer URI de tu User Pool:yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXXReemplaza
us-east-1_XXXXXXXXX con el ID de tu User Pool (lo encuentras en la consola de AWS Cognito). Con esto, Spring descarga automáticamente las claves públicas de Cognito para verificar las firmas.Security config básica
La configuración mínima para validar JWT:
SecurityConfig.kt
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.csrf { it.disable() }
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
}
.oauth2ResourceServer { oauth2 ->
oauth2.jwt()
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.build()
}
}Con esto, Spring Boot automáticamente descarga las claves públicas de Cognito, valida la firma del JWT, verifica que no haya expirado, y extrae los claims del usuario. Todo sin una sola línea de código de validación manual.
Agregar autorización por roles
Si necesitas controlar acceso por grupos de Cognito, necesitas un
JwtAuthenticationConverter que mapee el claim cognito:groups a roles de Spring Security:SecurityConfigWithRoles.kt
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfigWithRoles {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.csrf { it.disable() }
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
}
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { jwt ->
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
}
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.build()
}
@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val authoritiesConverter = JwtGrantedAuthoritiesConverter()
authoritiesConverter.setAuthorityPrefix("ROLE_")
authoritiesConverter.setAuthoritiesClaimName("cognito:groups")
val converter = JwtAuthenticationConverter()
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter)
return converter
}
}El
JwtGrantedAuthoritiesConverter lee el claim cognito:groups del token y lo convierte en authorities con el prefijo ROLE_. Así, un usuario en el grupo "ADMIN" de Cognito obtiene la authority ROLE_ADMIN en Spring Security.Crear los grupos en Cognito
bash
# Crear grupos
aws cognito-idp create-group \
--group-name "ADMIN" \
--user-pool-id us-east-1_XXXXXXXXX
aws cognito-idp create-group \
--group-name "USER" \
--user-pool-id us-east-1_XXXXXXXXX
# Asignar un usuario a un grupo
aws cognito-idp admin-add-user-to-group \
--user-pool-id us-east-1_XXXXXXXXX \
--username "test@example.com" \
--group-name "ADMIN"Endpoints protegidos
Controllers.kt
@RestController
@RequestMapping("/api/public")
class PublicController {
@GetMapping("/health")
fun healthCheck() = mapOf(
"status" to "OK",
"timestamp" to Instant.now().toString()
)
}
@RestController
@RequestMapping("/api/protected")
class ProtectedController {
@GetMapping("/profile")
fun getUserProfile(auth: JwtAuthenticationToken): Map<String, Any?> {
val jwt = auth.token
return mapOf(
"userId" to jwt.getClaimAsString("sub"),
"email" to jwt.getClaimAsString("email"),
"groups" to jwt.getClaimAsStringList("cognito:groups")
)
}
}
@RestController
@RequestMapping("/api/admin")
class AdminController {
@GetMapping("/dashboard")
@PreAuthorize("hasRole('ADMIN')")
fun adminDashboard() = mapOf(
"message" to "Solo ADMIN puede ver esto",
"timestamp" to Instant.now().toString()
)
}Probar con curl
bash
# Endpoint público (sin token)
curl http://localhost:8080/api/public/health
# Endpoint protegido (requiere token)
curl http://localhost:8080/api/protected/profile \
-H "Authorization: Bearer <tu-token-jwt>"
# Endpoint admin (requiere rol ADMIN)
curl http://localhost:8080/api/admin/dashboard \
-H "Authorization: Bearer <tu-token-jwt>"Las respuestas esperadas:
text
Sin token en endpoint protegido: HTTP 401 Unauthorized
Con token sin rol requerido: HTTP 403 Forbidden
Con token y rol correcto: HTTP 200 OKDebugging
Si los roles no funcionan, agrega un endpoint temporal para inspeccionar qué authorities tiene el usuario:
kotlin
@GetMapping("/debug/authorities")
fun debug(auth: JwtAuthenticationToken) = mapOf(
"authorities" to auth.authorities.map { it.authority },
"groups" to auth.token.getClaimAsStringList("cognito:groups"),
"allClaims" to auth.token.claims
)Checklist de problemas comunes
- ¿El usuario está asignado al grupo correcto en Cognito?
- ¿El claim
cognito:groupsaparece en el JWT? - ¿El nombre del grupo coincide exactamente? (case-sensitive)
- ¿Habilitaste
@EnableMethodSecuritypara usar@PreAuthorize?
En resumen
Con tres dependencias, una línea en
application.yml y una clase de configuración, tu backend valida tokens JWT de AWS Cognito automáticamente. Agregar roles es cuestión de un JwtAuthenticationConverter que mapee cognito:groups a authorities de Spring Security.Tu backend no maneja sesiones, no almacena estado, y no sabe nada del login. Solo valida lo que le llega en el header
Authorization. Esa separación de responsabilidades es lo que hace que esta arquitectura escale.