DEV 8 min

OpenAPI en Spring Boot 3: Documentación y Hardening de APIs

Aprende a integrar y configurar OpenAPI 3 con Springdoc en aplicaciones Spring Boot 3+. Cómo generar contratos de API, proteger los endpoints en producción y habilitar autenticación Bearer JWT en Swagger UI.

Escrito por Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

La Dualidad de OpenAPI 3: Eficiencia de Desarrollo vs. Fuga de Información

En la arquitectura de microservicios moderna, la especificación OpenAPI 3 (anteriormente Swagger) es el estándar indiscutible para documentar contratos de interfaz de programación de aplicaciones (APIs). Herramientas como springdoc-openapi automatizan la generación de esquemas JSON interactivos e interfaces gráficas web (Swagger UI), permitiendo a los desarrolladores explorar endpoints, generar SDKs de cliente tipados y ejecutar pruebas de integración de forma inmediata.

Sin embargo, en entornos corporativos, exponer la documentación técnica de una API en producción representa un vector crítico de *Fuga de Información (Information Disclosure)*. Durante la fase de reconocimiento de cualquier prueba de penetración o ataque dirigido, los ciberdelincuentes escanean activamente rutas habituales como /swagger-ui/index.html o /v3/api-docs.

El acceso no restringido a la documentación de la API expone:

  • El mapa topológico completo de endpoints del sistema, incluyendo rutas administrativas internas o endpoints en fase experimental.
  • Esquemas de datos detallados con nombres de campos, tipos, validaciones y modelos relacionales.
  • Parámetros ocultos susceptibles a inyecciones de parámetros masivos (Mass Assignment).
  • Versiones de librerías y servidores que facilitan la identificación de vulnerabilidades conocidas (CVEs).

Integrar OpenAPI en Spring Boot 3 exige un equilibrio riguroso: maximizar la productividad durante el ciclo de desarrollo garantizando al mismo tiempo un blindaje perimetral absoluto en los entornos de producción.


Arquitectura de Control de Acceso para OpenAPI y Swagger UI

Para impedir que atacantes externos aprovechen la documentación interactiva, el acceso a /v3/api-docs y /swagger-ui/** debe segregarse a nivel de pasarela perimetral (API Gateway) y cadena de filtros de Spring Security:

graph TD
    subgraph External_Traffic ["🌐 Tráfico Externo (Internet Público)"]
        PublicClient["Cliente Web / Móvil / Atacante"]
    end

    subgraph Internal_Traffic ["🏢 Red Interna Corporativa"]
        DevOperator["Ingeniero / SRE / Auditor"]
    end

    subgraph API_Gateway ["🛡️ API Gateway / Reverse Proxy"]
        PathRouter{"Enrutador de Rutas"}
    end

    subgraph Spring_Boot_Service ["⚙️ Microservicio Spring Boot 3"]
        SecurityFilterChain["Spring Security Filter Chain"]
        
        subgraph Endpoints ["Controladores"]
            BusinessAPI["APIs de Negocio: /api/v1/orders"]
            SwaggerDocs["Documentación: /v3/api-docs & /swagger-ui"]
        end
    end

    PublicClient -->|Petición HTTP| API_Gateway
    Internal_Traffic -->|Conexión VPN / Red Interna| API_Gateway
    API_Gateway --> PathRouter

    PathRouter -->|Ruta: /api/v1/**| SecurityFilterChain
    PathRouter -->|Ruta: /v3/api-docs desde Internet| BlockGateway["HTTP 404 / 403 (Bloqueo en el Edge)"]
    PathRouter -->|Ruta: /v3/api-docs desde VPN| SecurityFilterChain

    SecurityFilterChain -->|Token JWT Válido| BusinessAPI
    SecurityFilterChain -->|Rol 'ROLE_INTERNAL_DEV'| SwaggerDocs
    SecurityFilterChain -.->|Sin Permisos de Auditoría| Deny["HTTP 401 / 403"]

    classDef public fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef internal fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
    classDef edge fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef block fill:#ffebee,stroke:#d32f2f,stroke-width:2px;

    class PublicClient public;
    class DevOperator internal;
    class API_Gateway,PathRouter edge;
    class BlockGateway,Deny block;
    class SecurityFilterChain,BusinessAPI,SwaggerDocs internal;

1. Integración de springdoc-openapi en Spring Boot 3 (Java 21)

En Spring Boot 3 (basado en Spring Framework 6 y Jakarta EE 9/10), la integración oficial se realiza a través del starter modular springdoc-openapi-starter-webmvc-ui.

Configuración de Dependencias en pom.xml:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>

Parámetros Base en application.yml:

En lugar de aceptar las rutas por defecto, se recomienda ofuscar los prefijos y desactivar Swagger UI en los perfiles destinados a producción:

# Configuración base (desarrollo local)
springdoc:
  api-docs:
    path: /internal/docs/v3/api-docs
    enabled: true
  swagger-ui:
    path: /internal/docs/swagger-ui.html
    enabled: true
    operationsSorter: method
    tagsSorter: alpha
    display-request-duration: true

---
# Perfil de Producción (application-prod.yml)
spring:
  config:
    activate:
      on-profile: prod

springdoc:
  # Desactivar Swagger UI completamente en el entorno de producción público
  swagger-ui:
    enabled: false
  # Mantener JSON interno restringido exclusivamente a herramientas de monitorización
  api-docs:
    enabled: false

2. Configuración Programática y Soporte JWT Bearer (OAuth2 / OIDC)

Para que Swagger UI permita ejecutar peticiones interactivas contra endpoints protegidos mediante tokens JWT, es necesario registrar un esquema de seguridad (SecurityScheme) de tipo HTTP Bearer en la configuración de OpenAPI:

package es.secdevoops.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class OpenApiSecurityConfig {

    private static final String SECURITY_SCHEME_NAME = "BearerAuthentication";

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Corporate Microservices API")
                .description("API RESTful empresarial con seguridad JWT y control de acceso RBAC.")
                .version("1.4.0")
                .contact(new Contact()
                    .name("Equipo de Ciberseguridad")
                    .email("security@empresa.local"))
                .license(new License().name("Proprietary")))
            .servers(List.of(
                new Server().url("https://api.empresa.local").description("Entorno de Producción"),
                new Server().url("http://localhost:8080").description("Entorno Local de Pruebas")
            ))
            // Requerimiento de seguridad global en todos los endpoints
            .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
            .components(new Components()
                .addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme()
                    .name(SECURITY_SCHEME_NAME)
                    .type(SecuritySchemeType.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
                    .description("Introduce el token JWT con prefijo 'Bearer '")));
    }
}

3. Hardening de Modelos de Datos y Prevención de Exposición de Secretos

Uno de los errores más graves al generar contratos OpenAPI automáticamente a partir de clases Java es exponer campos internos del modelo de base de datos (entidades JPA).

Antipatrón: Exponer Entidades JPA Directamente

Si un controlador expone la entidad UserEntity directamente, Swagger documentará atributos como passwordHash, failedLoginAttempts o tenantId, revelando la estructura interna de la base de datos a clientes externos.

Práctica Recomendada: DTO Records Inmutables y @Schema(hidden = true)

Las capas de API deben operar exclusivamente mediante DTO Records de Java 21, decorados con anotaciones de OpenAPI que oculten o restrinjan los metadatos:

package es.secdevoops.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Schema(description = "DTO de transferencia para la creación de cuentas de usuario")
public record CreateUserRequest(
    @Schema(description = "Nombre de usuario único", example = "samir.dev", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank @Size(min = 3, max = 50)
    String username,

    @Schema(description = "Correo electrónico corporativo", example = "samir@empresa.local")
    @NotBlank @Email
    String email,

    @Schema(description = "Contraseña en texto plano para alta", accessMode = Schema.AccessMode.WRITE_ONLY)
    @NotBlank @Size(min = 12)
    String password,

    @Schema(hidden = true) // Ocultar metadatos internos en la documentación
    String internalAuditTrackingId
) {}
  • accessMode = Schema.AccessMode.WRITE_ONLY: Indica que el campo solo se acepta en peticiones de entrada (POST/PUT), impidiendo que aparezca en los esquemas de respuesta del JSON generado.
  • @Schema(hidden = true): Excluye el campo por completo de la documentación generada, evitando fugas de parámetros internos.

4. Securización de los Endpoints de Documentación con Spring Security

Si la organización requiere mantener Swagger UI accesible en entornos pre-productivos o accesibles por desarrolladores, el acceso a las rutas de documentación debe restringirse mediante autenticación en la cadena de filtros de Spring Security:

package es.secdevoops.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // En APIs stateless con JWT
            .authorizeHttpRequests(auth -> auth
                // Endpoints públicos de negocio
                .requestMatchers("/api/v1/auth/**", "/actuator/health").permitAll()
                // Restringir Swagger UI y OpenAPI JSON a usuarios con rol específico
                .requestMatchers(
                    "/internal/docs/**",
                    "/swagger-ui/**",
                    "/v3/api-docs/**"
                ).hasAuthority("SCOPE_internal:read_docs")
                // El resto de la API requiere autenticación general
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

        return http.build();
    }
}

Con esta regla de autorización, cualquier petición no autenticada a la documentación responderá con HTTP 401 Unauthorized, bloqueando escáneres de vulnerabilidades automatizados.


5. Segregación por Grupos: GroupedOpenApi y Documentación Selectiva

En sistemas que exponen tanto APIs de consumo público para integradores externos como APIs internas para herramientas de backoffice, la configuración monolítica de OpenAPI revela endpoints que los integradores jamás deberían conocer.

Springdoc permite solventar esto mediante la definición de beans GroupedOpenApi, publicando contratos aislados en paths independientes:

package es.secdevoops.config;

import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiGroupingConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
            .group("public-v1")
            .pathsToMatch("/api/v1/public/**")
            .build();
    }

    @Bean
    public GroupedOpenApi internalAdminApi() {
        return GroupedOpenApi.builder()
            .group("internal-admin")
            .pathsToMatch("/api/v1/admin/**", "/actuator/**")
            .build();
    }
}

Combinando esta segregación con las reglas del API Gateway, la pasarela perimetral puede servir públicamente únicamente el grupo /v3/api-docs/public-v1, bloqueando por completo la existencia de /v3/api-docs/internal-admin a cualquier cliente externo.


6. Verificación de Contratos y Linting en CI/CD con Spectral

La documentación de APIs no debe evaluarse únicamente en tiempo de ejecución. En el marco de un pipeline DevSecOps maduro, los contratos OpenAPI generados deben validarse de forma estática en la fase de integración continua utilizando herramientas de linting como Spectral (de Stoplight):

# Exportar especificación OpenAPI generada y ejecutar linter de estilo y seguridad
npx @stoplight/spectral-cli lint openapi.json --ruleset spectral:oas

Spectral permite detectar automáticamente:

  • Operaciones que carecen de códigos de respuesta de error (400, 401, 403, 500).
  • Parámetros sin descripción o sin ejemplos válidos.
  • Endpoints que aceptan payloads sin definir restricciones de longitud o formato.
  • Ausencia de esquemas de autenticación en endpoints de mutación crítica.

7. Filtrado en el Borde: Bloqueo en API Gateway

En arquitecturas distribuidas con API Gateway (Spring Cloud Gateway, Kong, Envoy o AWS API Gateway), la mejor práctica consiste en bloquear completamente las rutas de documentación en el borde de la red para clientes públicos:

# Configuración en Spring Cloud Gateway
spring:
  cloud:
    gateway:
      routes:
        - id: backend-service-public
          uri: lb://backend-service
          predicates:
            - Path=/api/v1/**
          filters:
            # Eliminar cabeceras que delaten frameworks
            - RemoveResponseHeader=X-Powered-By
            - RemoveResponseHeader=Server

        # Bloquear activamente cualquier intento de acceder a OpenAPI desde fuera
        - id: block-swagger-docs
          uri: noop://
          predicates:
            - Path=/**/api-docs/**, /**/swagger-ui/**
          filters:
            - SetStatus=404

Resumen y Checklist de Hardening para OpenAPI en Spring Boot

  • [x] Starter springdoc-openapi-starter-webmvc-ui integrado y configurado con dependencias vigentes.
  • [x] Prefijos de ruta modificados para evitar rutas predecibles por defecto (/swagger-ui.html).
  • [x] Swagger UI desactivado completamente en perfiles de producción mediante enabled: false.
  • [x] Esquema de seguridad JWT Bearer registrado para pruebas autenticadas en entornos de desarrollo.
  • [x] DTOs aislados de entidades JPA con @Schema(hidden = true) y WRITE_ONLY en campos sensibles.
  • [x] Rutas de documentación protegidas en Spring Security mediante roles o filtradas en el API Gateway.

Si necesitas auditar la seguridad de tus APIs REST en Spring Boot, proteger endpoints corporativos frente a fugas de información o implementar arquitecturas de microservicios con OAuth2 y JWT, puedes consultar mis Servicios de Ciberseguridad y Consultoría o ponerte en contacto conmigo a través de Sobre mí.