Capa de seguridad

El arquetipo monta un único SecurityFilterChain con varios mecanismos de autenticación habilitables independientemente vía app.security.*. Cada request usa exactamente un mecanismo; si trae credenciales de varios a la vez, se rechaza con 401.

Mecanismos

Mecanismo Credencial Cuándo

pre-authenticated

Header X-User-Id

Modo por defecto. Confía en la identidad ya validada por el API gateway aguas arriba.

jwt

Header Authorization: Bearer <token>

Validación local contra issuers configurados vía JWKS / OIDC discovery.

Propiedades

app:
  security:
    pre-authenticated:
      enabled: true                  # default
    jwt:
      enabled: false                 # default
      issuers:
        - issuer-uri: ...
          audiences: [...]
          tenant-claim: ...          # opcional, por issuer
          authorities-claim: ...     # opcional, por issuer
          unsigned: false            # default
Propiedad Tipo Significado

pre-authenticated.enabled

boolean

Activa RequestHeaderAuthenticationFilter que lee X-User-Id.

jwt.enabled

boolean

Activa el resource server OAuth2 sobre la SecurityFilterChain.

jwt.issuers[].issuer-uri

URL

URI del issuer; coincide con iss del token. JWKS se descubre vía /.well-known/openid-configuration.

jwt.issuers[].audiences

List<String>

El token autentica si su claim aud contiene al menos una. Vacío desactiva la validación de audience para ese issuer.

jwt.issuers[].tenant-claim

String

Nombre del claim del token a comparar con X-Tenant-Id. Sin valor, el cruce identidad↔tenant no aplica para este issuer.

jwt.issuers[].authorities-claim

String

Nombre del claim que produce las authorities para este issuer. Sin valor, Spring usa los defaults (scope/scp).

jwt.issuers[].unsigned

boolean

Si true, salta verificación de firma y OIDC discovery. Solo para desarrollo local — emite WARN al arrancar.

Los nombres de los claims se configuran por issuer porque los IDPs no coinciden en convenciones: Keycloak usa realm_access.roles, Auth0 usa permissions, Azure AD usa roles o scp, Okta usa groups. Igual aplica al claim del tenant.

Cadena de filtros

Orden relevante (omitiendo filtros no involucrados en auth):

  1. TenantContextFilter — corre antes que la SecurityFilterChain. Resuelve X-Tenant-Id y popula TenantContextHolder.

  2. CredentialAmbiguityFilter — activo solo cuando hay 2+ mecanismos habilitados. Cuenta credenciales presentes y rechaza con 401 si hay más de una.

  3. RequestHeaderAuthenticationFilter — activo si pre-authenticated.enabled=true. Lee X-User-Id.

  4. BearerTokenAuthenticationFilter — activo si jwt.enabled=true. Lee Authorization: Bearer.

Validadores JWT

Cada issuer construye un DelegatingOAuth2TokenValidator<Jwt> con:

Validador Acción

JwtTimestampValidator

Verifica exp y nbf con clock skew de 60 s.

JwtIssuerValidator

Verifica que iss coincida con issuer-uri.

JwtClaimValidator<aud>

Si audiences no está vacío, verifica que aud contenga al menos una.

JwtTenantClaimValidator

Si tenant-claim está configurado, verifica que jwt.<claim> == TenantContextHolder.getTenantId().

Fallo en cualquier validador → JwtValidationException → 401.

Endpoint de diagnóstico

GET /v1/whoami (commons/adapter/in/web/WhoAmIController) devuelve la identidad resuelta. Sirve para verificar la cadena de auth end-to-end sin tocar un endpoint del dominio.

{
  "subject": "user-42",
  "mechanism": "jwt",
  "authorities": ["admin"]
}

mechanism se infiere del subtipo concreto de Authentication: pre-authenticated, jwt, o unknown. Tenant no se expone — la identidad y el contexto multitenancy son conceptos separados.

Puntos de extensión

Necesidad Cómo

Validar claims custom (p. ej. aud con formato no estándar)

Implementar OAuth2TokenValidator<Jwt> y agregarlo en JwtConfigurer.buildValidator(…​). La cadena delegada está pensada para extender.

Mapeo claims → authorities no trivial (prefijos, lookup en BD, transformaciones)

Reemplazar el JwtAuthenticationConverter configurado en JwtConfigurer.buildConverter(). Spring acepta cualquier Converter<Jwt, AbstractAuthenticationToken>.

Comparación de tenant con lógica adicional (transformar el claim antes de comparar)

Reemplazar JwtTenantClaimValidator por una implementación propia.

Issuer resolution dinámica (p. ej. por tenant)

Reemplazar el JwtIssuerAuthenticationManagerResolver que monta JwtConfigurer.issuerResolver(). Spring acepta cualquier AuthenticationManagerResolver<HttpServletRequest>.

Ver también