Como habilitar autenticacion JWT

Pre-authenticated queda como modo por defecto del arquetipo (X-User-Id propagado por el API gateway). Para que el servicio valide tokens JWT por sí mismo, sin depender del gateway, se habilita el flujo OAuth2 resource server con uno o varios issuers de confianza.

1. Habilitar el mecanismo

En application.yaml:

app:
  security:
    jwt:
      enabled: true

Con esto Spring Security agrega BearerTokenAuthenticationFilter a la cadena. Pre-authenticated y JWT pueden coexistir; cuando ambos están habilitados y una request trae las dos credenciales a la vez, CredentialAmbiguityFilter rechaza con 401.

2. Configurar issuers de confianza

Cada issuer requiere su issuer-uri (de donde se descubre el JWKS vía OIDC /.well-known/openid-configuration) y la audiencia esperada:

app:
  security:
    jwt:
      enabled: true
      issuers:
        - issuer-uri: https://idp-corporate.example/realms/services
          audiences: [orders-service]
        - issuer-uri: https://idp-partner.example
          audiences: [orders-service]

Para cada issuer:

  • iss debe coincidir con issuer-uri.

  • aud debe contener al menos una de las audiences listadas.

  • exp debe estar en el futuro (clock skew estándar de Spring: 60 s).

  • La firma se valida contra el JWKS del issuer.

Token cuyo iss no coincida con ningún issuer configurado → 401.

3. Mapear claims a authorities

Los IDPs no se ponen de acuerdo en el nombre del claim de authorities: Keycloak usa realm_access.roles o resource_access.{client}.roles, Auth0 usa permissions, Azure AD usa roles (app roles) o scp (scopes), Okta usa groups. Por eso authorities-claim se configura por issuer:

app:
  security:
    jwt:
      issuers:
        - issuer-uri: https://idp-corporate.example/realms/services
          audiences: [orders-service]
          authorities-claim: roles
        - issuer-uri: https://idp-partner.example
          audiences: [orders-service]
          authorities-claim: permissions

Las authorities resultantes son los valores del claim, sin prefijo. @PreAuthorize("hasAuthority('admin')") funciona sin ajustes extra.

Sin authorities-claim, Spring lee scope/scp por default.

4. Cruzar identidad con tenant

Si el JWT trae el tenant en un claim — y el nombre del claim también varía por IDP — el arquetipo valida que coincida con X-Tenant-Id:

app:
  security:
    jwt:
      issuers:
        - issuer-uri: https://idp-corporate.example/realms/services
          audiences: [orders-service]
          tenant-claim: tenant_id
        - issuer-uri: https://idp-partner.example
          audiences: [orders-service]
          tenant-claim: tid

Casos de rechazo:

  • Token sin el claim configurado → 401.

  • Request sin X-Tenant-Id → 401.

  • Valor del claim ≠ X-Tenant-Id → 401.

Sin tenant-claim para un issuer, el cruce identidad↔tenant se omite para ese issuer. Útil cuando el JWT no transporta tenant y se confía exclusivamente en el header.

5. Verificar con WhoAmI

GET /v1/whoami (montado en commons/adapter/in/web) devuelve la identidad resuelta:

export BEARER_TOKEN=eyJhbGciOiJSUzI1NiJ9...

curl http://localhost:8080/v1/whoami \
    -H 'X-Tenant-Id: 674b414f-27b5-461b-a8da-933326669018' \
    -H 'X-Application: BACKOFFICE' \
    -H "Authorization: Bearer $BEARER_TOKEN"

X-Application es obligatorio: RequestContextInterceptor lo exige antes de que la cadena de seguridad se ejecute. Sin él la response es 400, no 401.

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

Si la cadena de auth falla por cualquier validador (iss, aud, exp, firma, tenant-claim), el endpoint devuelve 401.

Modo unsigned para desarrollo local

Para probar la integración sin levantar un IDP, un issuer puede declararse unsigned:

app:
  security:
    jwt:
      enabled: true
      issuers:
        - issuer-uri: http://localhost/local
          audiences: [dev]
          unsigned: true

Con unsigned: true:

  • Se omite OIDC discovery y la verificación criptográfica.

  • iss, aud, exp, tenant-claim y authorities-claim siguen aplicando.

  • Al arrancar, el servicio emite un WARN por cada issuer en este modo.

El token se genera en jwt.io con alg: none o con cualquier firma — el decoder no la verifica.

unsigned: true no debe llegar nunca a un entorno productivo. Los tokens son trivialmente falsificables. Reservar para desarrollo local y test de integración.

Ver también