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-uiintegrado 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)yWRITE_ONLYen 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í.