Step 2: Gateway Swagger Integration – Spring Documentation Series

🌐 Step 2: Gateway Swagger Integration

πŸŽ₯ YouTube Video: Coming Soon – Subscribe to JavaiOS Channel


Swagger UI Integration via Spring Cloud Gateway 🌐

This document explains how Swagger UI is aggregated across multiple microservices behind a Spring Cloud Gateway, detailing the request flow and how security is managed for a unified documentation experience.

In the AdventureTube microservice architecture, it requires a two-phase integration approach to properly aggregate documentation across all services while maintaining security and proper routing.


🧭 Overview

Spring Cloud Gateway serves as the central entry point that aggregates Swagger documentation from multiple microservices into a single, unified interface. This approach provides developers with one place to discover, understand, and test all available APIs across the entire microservice ecosystem.

Key Benefits:

  • Centralized Documentation – Single URL for all API documentation
  • Unified Testing – Test APIs across different services from one interface
  • Consistent Security – Standardized authentication across all documented endpoints
  • Service Discovery – Automatic aggregation of new microservice documentation

πŸ› οΈ Two-Phase Integration Overview

πŸ”Ή Phase 1: Individual Service Configuration

Each microservice is configured with SpringDoc OpenAPI to generate Swagger documentation. These are then aggregated through the Gateway using predefined routes and exposed under a unified Swagger UI.

Reference: Complete Phase 1 implementation in Step 1

πŸ”Ή Phase 2: Gateway Security Configuration

After setting up the gateway forwarding, security configurations within each microservice must be adjusted to permit access to documentation endpoints (/v3/api-docs, /swagger-ui/**) while still protecting business logic routes with JWT-based filters.


🌊 Complete Request Flow Analysis

1. Initial Swagger UI Request 🎯

A user accesses the Swagger UI at:

https://api.adventuretube.net/swagger-ui/index.html

This triggers SpringDoc in the gateway-service, using the following configuration:

springdoc:
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    urls:
      - name: Auth Service
        url: /auth-service/v3/api-docs
      - name: Member Service
        url: /member-service/v3/api-docs
      - name: Web Service
        url: /web-service/v3/api-docs
      - name: Geospatial Service
        url: /geo-service/v3/api-docs
  api-docs:
    enabled: true

These urls define the destinations Swagger UI will call through the gateway to aggregate documentation from all microservices.


2. Gateway Routing and Security πŸ”

In GatewayConfig.java, all primary API routes (e.g., /auth/**, /member/**, etc.) are configured with .filters(f -> f.filter(filter)). This means every incoming request is:

  • Intercepted by the global custom AuthenticationFilter
  • Forwarded to the appropriate microservice with the /XXX-service prefix stripped

Example: /auth-service/xyz is forwarded as /xyz to auth-service

πŸ” RouterValidator Configuration

The AuthenticationFilter uses RouterValidator to determine which endpoints should bypass authentication:

// Open endpoints that bypass JWT authentication
List.of(
  "^/auth-service/v3/api-docs.*",
  "^/member-service/v3/api-docs.*", 
  "^/web-service/v3/api-docs.*",
  "^/geo-service/v3/api-docs.*",
  "^/swagger-ui.*"
)

These paths are considered open endpoints, so while the filter still runs, it allows these requests through without token validation.


3. Route Forwarding Configuration πŸš€

Each Swagger documentation route uses .stripPrefix(1) to clean up the path before forwarding:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        // Swagger documentation routes
        .route("auth-docs", r -> r.path("/auth-service/v3/api-docs")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://auth-service"))
        
        .route("member-docs", r -> r.path("/member-service/v3/api-docs")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://member-service"))
        
        .route("web-docs", r -> r.path("/web-service/v3/api-docs")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://web-service"))
        
        .route("geo-docs", r -> r.path("/geo-service/v3/api-docs")
            .filters(f -> f.stripPrefix(1))
            .uri("lb://geo-service"))
        
        // Main API routes with authentication
        .route("auth-service", r -> r.path("/auth-service/**")
            .filters(f -> f.stripPrefix(1).filter(filter))
            .uri("lb://auth-service"))
        
        // Additional service routes...
        .build();
}

Path Transformation:

  • From: /auth-service/v3/api-docs
  • To: /v3/api-docs (inside the target microservice)

🧩 Microservice Swagger Handling

πŸ” Auth Service Security Configuration

When the gateway forwards a request to /auth-service/v3/api-docs, the route is stripped to /v3/api-docs and passed to auth-service.

  1. Spring Security Filter Chain Engagement: The request hits auth-service‘s Spring Security configuration:
@Bean
@Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
        .csrf(AbstractHttpConfigurer::disable)
        .securityMatcher("/auth/**")
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers(
                "/auth/register",
                "/auth/login", 
                "/auth/refreshToken",
                "/auth/logout",
                "/swagger-ui.html",
                "/swagger-ui/**",
                "/v3/api-docs",
                "/v3/api-docs/**"
            ).permitAll()
            .anyRequest().hasRole("ADMIN")
        )
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .authenticationProvider(customAuthenticationProvider)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
    
    return httpSecurity.build();
}

Key Security Configurations:

  • Open Documentation Endpoints: /v3/api-docs and /swagger-ui/** are permitted without authentication
  • Protected Business Logic: All other /auth/** endpoints require ADMIN role
  • Stateless Session: No session state maintained for documentation requests
  • JWT Filter Integration: Authentication filter runs but permits documentation access

πŸ”„ Complete Flow Diagram

User Request: https://api.adventuretube.net/swagger-ui/index.html
        ↓
Gateway SpringDoc loads aggregated Swagger UI
        ↓
Swagger UI requests: /auth-service/v3/api-docs
        ↓
Gateway AuthenticationFilter (permits documentation endpoints)
        ↓
Route forwarding: /auth-service/v3/api-docs β†’ /v3/api-docs
        ↓
Auth Service Spring Security (permits /v3/api-docs)
        ↓
Auth Service SpringDoc generates OpenAPI specification
        ↓
Response returns to Gateway β†’ Swagger UI
        ↓
User sees unified documentation interface

βš™οΈ Implementation Best Practices

1. Gateway Configuration

@Configuration
public class GatewayConfig {
    
    @Autowired
    private AuthenticationFilter filter;
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // Documentation routes (no authentication filter)
            .route("auth-docs", r -> r.path("/auth-service/v3/api-docs")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://auth-service"))
            
            // API routes (with authentication filter)
            .route("auth-service", r -> r.path("/auth-service/**")
                .filters(f -> f.stripPrefix(1).filter(filter))
                .uri("lb://auth-service"))
            
            .build();
    }
}

2. Service Discovery Integration

# Gateway application.yml
springdoc:
  swagger-ui:
    urls:
      - name: Auth Service
        url: /auth-service/v3/api-docs
      - name: Member Service  
        url: /member-service/v3/api-docs
    # Automatically discover services from Eureka
    use-management-port: false
    disable-swagger-default-url: true

3. CORS Configuration for Documentation

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOriginPatterns(Arrays.asList("*"));
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    configuration.setAllowedHeaders(Arrays.asList("*"));
    configuration.setAllowCredentials(true);
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/v3/api-docs/**", configuration);
    source.registerCorsConfiguration("/swagger-ui/**", configuration);
    
    return source;
}

🚨 Common Issues and Solutions

Issue 1: Internal URLs in Documentation

Problem: Swagger UI shows internal service URLs instead of gateway URLs

Solution: Add @OpenAPIDefinition to each microservice:

@OpenAPIDefinition(
    servers = {
        @Server(url = "https://api.adventuretube.net", description = "AdventureTube API Gateway")
    }
)
@SpringBootApplication
public class AuthServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}

Issue 2: Authentication Errors for Documentation

Problem: Documentation endpoints require authentication

Solution: Ensure proper security configuration in each service:

// Add to security configuration
.requestMatchers(
    "/v3/api-docs",
    "/v3/api-docs/**", 
    "/swagger-ui.html",
    "/swagger-ui/**"
).permitAll()

Issue 3: Gateway Route Conflicts

Problem: Documentation routes conflict with API routes

Solution: Order routes correctly – documentation routes before API routes:

return builder.routes()
    // Documentation routes first (more specific)
    .route("auth-docs", r -> r.path("/auth-service/v3/api-docs")...)
    
    // API routes second (more general)  
    .route("auth-service", r -> r.path("/auth-service/**")...)
    .build();

πŸ“Š Performance Considerations

Caching Strategy

# Enable caching for API docs
springdoc:
  cache:
    disabled: false
  api-docs:
    resolve-schema-properties: true
    
# Gateway-level caching
spring:
  cloud:
    gateway:
      httpclient:
        pool:
          max-connections: 100
          max-idle-time: 30s

Load Balancing for Documentation

// Custom load balancer configuration
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
    return WebClient.builder()
        .filter(ExchangeFilterFunction.ofRequestProcessor(
            clientRequest -> {
                // Add custom headers for documentation requests
                if (clientRequest.url().getPath().contains("/v3/api-docs")) {
                    return Mono.just(ClientRequest.from(clientRequest)
                        .header("Cache-Control", "max-age=300")
                        .build());
                }
                return Mono.just(clientRequest);
            }
        ));
}

βœ… Implementation Checklist

Gateway Configuration βœ…

  • βœ… SpringDoc aggregation configuration
  • βœ… Route definitions for documentation endpoints
  • βœ… Authentication filter bypass for docs
  • βœ… CORS configuration for cross-origin requests

Service Security βœ…

  • βœ… Documentation endpoints permitted in security config
  • βœ… JWT filter integration maintained
  • βœ… Business logic endpoints protected
  • βœ… OpenAPI server URL configuration

Testing & Validation βœ…

  • βœ… Unified Swagger UI accessible
  • βœ… All services documentation loading
  • βœ… Authentication working for protected endpoints
  • βœ… Documentation endpoints accessible without tokens

πŸš€ Continue Your Documentation Journey

🧭 Next Steps in Spring Documentation:

Part of the AdventureTube technical blog series supporting the JavaiOS YouTube channel.

Leave a Comment

Your email address will not be published. Required fields are marked *