The OpenAPI 3 Duality: Developer Velocity vs. Information Disclosure
In modern microservices architecture, the OpenAPI 3 specification (formerly Swagger) serves as the industry standard for documenting Application Programming Interface (API) contracts. Libraries such as springdoc-openapi automate the generation of interactive JSON schemas and web-based exploratory user interfaces (Swagger UI), enabling engineering teams to inspect endpoints, generate typed client SDKs, and execute automated integration tests seamlessly.
However, within enterprise production environments, publicly exposing interactive API documentation constitutes a critical Information Disclosure vulnerability. During the reconnaissance phase of penetration tests or targeted cyber attacks, threat actors scan for common routes like /swagger-ui/index.html or /v3/api-docs.
Unrestricted access to API documentation reveals:
- A comprehensive topological map of endpoints, including unreleased beta routes and internal administrative paths.
- Granular data models detailing field names, data types, validation constraints, and database relationships.
- Hidden parameter keys susceptible to Mass Assignment and object manipulation attacks.
- Framework, runtime, and server version identifiers that facilitate known CVE targeting.
Integrating OpenAPI into Spring Boot 3 requires a disciplined balance: maximizing engineering productivity during development cycles while enforcing perimeter isolation in production environments.
Access Control Architecture for OpenAPI and Swagger UI
To prevent external threat actors from leveraging interactive documentation, access to /v3/api-docs and /swagger-ui/** must be segregated at the perimeter API Gateway and the Spring Security filter chain:
graph TD
subgraph External_Traffic ["🌐 External Ingress (Public Internet)"]
PublicClient["Public Client / Mobile / Threat Actor"]
end
subgraph Internal_Traffic ["🏢 Internal Corporate Network"]
DevOperator["Engineer / SRE / Auditor (VPN)"]
end
subgraph API_Gateway ["🛡️ API Gateway / Reverse Proxy"]
PathRouter{"Path Routing Engine"}
end
subgraph Spring_Boot_Service ["⚙️ Spring Boot 3 Microservice"]
SecurityFilterChain["Spring Security Filter Chain"]
subgraph Endpoints ["Controllers"]
BusinessAPI["Business APIs: /api/v1/orders"]
SwaggerDocs["Documentation: /v3/api-docs & /swagger-ui"]
end
end
PublicClient -->|HTTP Request| API_Gateway
Internal_Traffic -->|Encrypted VPN Channel| API_Gateway
API_Gateway --> PathRouter
PathRouter -->|Route: /api/v1/**| SecurityFilterChain
PathRouter -->|Route: /v3/api-docs from Internet| BlockGateway["HTTP 404 / 403 (Perimeter Block)"]
PathRouter -->|Route: /v3/api-docs from VPN| SecurityFilterChain
SecurityFilterChain -->|Valid JWT Bearer| BusinessAPI
SecurityFilterChain -->|Authority: 'ROLE_INTERNAL_DEV'| SwaggerDocs
SecurityFilterChain -.->|Insufficient Scope| 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. Integrating springdoc-openapi in Spring Boot 3 (Java 21)
In Spring Boot 3 (leveraging Spring Framework 6 and Jakarta EE 10), official integration is provided via the modular springdoc-openapi-starter-webmvc-ui dependency.
Build Configuration in pom.xml:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
Application Baseline in application.yml:
Rather than using predictable default URLs, customize path prefixes and disable interactive UI interfaces in production deployment profiles:
# Local development profile
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
---
# Production profile (application-prod.yml)
spring:
config:
activate:
on-profile: prod
springdoc:
# Completely disable Swagger UI in public production environments
swagger-ui:
enabled: false
# Disable raw schema generation on public surfaces
api-docs:
enabled: false
2. Programmatic Configuration and JWT Bearer (OAuth2 / OIDC) Support
To enable authenticated testing directly within Swagger UI for protected endpoints, declare an HTTP Bearer SecurityScheme within the OpenAPI bean configuration:
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("Enterprise RESTful API with JWT authentication and RBAC controls.")
.version("1.4.0")
.contact(new Contact()
.name("Cybersecurity Architecture Team")
.email("security@enterprise.local"))
.license(new License().name("Proprietary")))
.servers(List.of(
new Server().url("https://api.enterprise.local").description("Production Environment"),
new Server().url("http://localhost:8080").description("Local Testing Environment")
))
// Global security requirement across documented endpoints
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("Enter your JWT token with 'Bearer ' prefix")));
}
}
3. Data Model Hardening: Preventing Internal Schema Exposure
A severe vulnerability pattern is generating OpenAPI schemas directly from underlying persistence entities (JPA entities).
The JPA Direct Exposure Antipattern:
Exposing a database entity like UserEntity in a REST controller results in Swagger serializing fields such as passwordHash, failedLoginAttempts, or tenantId, leaking internal database topologies.
Production Solution: Immutable Java 21 Records and @Schema(hidden = true)
Application controllers should operate strictly over Data Transfer Objects (DTOs), annotated with OpenAPI constraints to conceal sensitive metadata:
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 = "User account registration request payload")
public record CreateUserRequest(
@Schema(description = "Unique user account handle", example = "samir.dev", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank @Size(min = 3, max = 50)
String username,
@Schema(description = "Corporate email address", example = "samir@enterprise.local")
@NotBlank @Email
String email,
@Schema(description = "Account plaintext password", accessMode = Schema.AccessMode.WRITE_ONLY)
@NotBlank @Size(min = 12)
String password,
@Schema(hidden = true) // Hide internal telemetry fields from generated documentation
String internalAuditTrackingId
) {}
accessMode = Schema.AccessMode.WRITE_ONLY: Dictates that the property is strictly accepted in inbound payloads (POST/PUT), preventing inclusion in outbound response schemas.@Schema(hidden = true): Excises the field entirely from generated contract specifications.
4. Securing Documentation Endpoints in Spring Security
When an organization requires accessible Swagger documentation in non-production environments or across internal developer networks, endpoints must be guarded via 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()) // Disabled for stateless JWT APIs
.authorizeHttpRequests(auth -> auth
// Public business endpoints
.requestMatchers("/api/v1/auth/**", "/actuator/health").permitAll()
// Restrict Swagger UI and OpenAPI JSON to authenticated developer roles
.requestMatchers(
"/internal/docs/**",
"/swagger-ui/**",
"/v3/api-docs/**"
).hasAuthority("SCOPE_internal:read_docs")
// All other business endpoints require standard authentication
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}
Any unauthenticated attempt to query API documentation routes triggers an immediate HTTP 401 Unauthorized response, blocking automated reconnaissance scanners.
5. API Group Segregation: GroupedOpenApi and Selective Documentation
In systems exposing both external public-facing APIs for partner integration and sensitive internal APIs for administrative operations, a monolithic OpenAPI specification inadvertently leaks private operational endpoints.
Springdoc addresses this cleanly via GroupedOpenApi beans, publishing isolated contracts along distinct URI pathways:
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();
}
}
By coupling grouped endpoints with edge routing rules, public gateways selectively expose only /v3/api-docs/public-v1 to external clients while completely hiding /v3/api-docs/internal-admin.
6. Contract Verification and CI/CD Security Linting with Spectral
API documentation must not only be managed at runtime; in mature DevSecOps deployment pipelines, OpenAPI specifications should undergo static analysis during automated CI/CD builds using schema linters such as Spectral (by Stoplight):
# Export generated OpenAPI specification and run automated security and style linting
npx @stoplight/spectral-cli lint openapi.json --ruleset spectral:oas
Spectral automated rulesets enforce critical quality gates:
- Flagging operations that fail to define error response status codes (
400,401,403,500). - Enforcing explicit parameter formats and boundary validations.
- Detecting unconstrained payloads lacking size limits or strict format masks.
- Ensuring every state-modifying mutating endpoint declares explicit authentication schemes.
7. Perimeter Enforcement: Gateway Filtering
Within microservice topologies fronted by an API Gateway (Spring Cloud Gateway, Envoy, Kong, or AWS API Gateway), enforce edge filtering rules to strip documentation paths entirely from public routing:
# Spring Cloud Gateway configuration
spring:
cloud:
gateway:
routes:
- id: backend-service-public
uri: lb://backend-service
predicates:
- Path=/api/v1/**
filters:
# Strip fingerprinting response headers
- RemoveResponseHeader=X-Powered-By
- RemoveResponseHeader=Server
# Actively block external attempts to access OpenAPI endpoints
- id: block-swagger-docs
uri: noop://
predicates:
- Path=/**/api-docs/**, /**/swagger-ui/**
filters:
- SetStatus=404
Enterprise OpenAPI Hardening Checklist
- [x] Integrated
springdoc-openapi-starter-webmvc-uion modern Spring Boot 3 standards. - [x] Customized default documentation paths to prevent automated endpoint reconnaissance.
- [x] Swagger UI completely disabled in production profiles via
springdoc.swagger-ui.enabled: false. - [x] Registered JWT Bearer security schemes for authenticated testing in developer environments.
- [x] Decoupled JPA entities from API contracts using immutable DTO records and
@Schema(hidden = true). - [x] Documentation endpoints filtered at the API Gateway perimeter to block public discovery.
If you need to audit your organization's Spring Boot REST APIs, remediate information disclosure risks, or architect secure microservices with OAuth2 and JWT, explore my Cybersecurity Consulting Services or reach out directly via About Me.