Springdoc OpenAPI with Spring Cloud Gateway

Springdoc OpenAPI with Spring Cloud Gateway

Introduction

This article is a part of a series of articles on Springdoc OpenAPI and Spring Cloud Gateway.

  1. Springdoc OpenAPI with OAuth2 Security
  2. Springdoc OpenAPI with Spring Cloud Gateway

To understand this article, you should have a basic understanding of Spring Boot, Spring Security, and OAuth2. If you are new to Spring Boot, Spring Security, and OAuth2, you can refer to the following article and its series:

Topics that we will cover in this article:

  • Benefits of using Springdoc OpenAPI with Spring Cloud Gateway
  • Aggregates OpenAPI (Swagger) documentation into Spring Cloud Gateway
  • Supports OAuth2 security with JWT tokens
  • Try it out with Swagger UI using the JWT token

Benefits of using Springdoc OpenAPI with Spring Cloud Gateway

  • Aggregates OpenAPI (Swagger) documentation into Spring Cloud Gateway
  • Simplifies the configuration of Authorization Server and Resource Server regarding redirect URIs and CORS
  • Supports OAuth2 security with JWT tokens
  • Provides a user-friendly interface to try out the endpoints of the Resource Servers

CORS Configuration

CORS errors happen when a web application tries to make a request to a different domain in a web browser. CORS (Cross-Origin Resource Sharing) is a security feature that limits the resources a web page can access from other domains.

For more information about CORS, you can refer to the following article:

When making requests from the Swagger UI on Spring Cloud Gateway to the Resource Servers, we need to configure CORS on the Resource Servers to allow requests from the Gateway. Additionally, we must enable CORS on the Authorization Server to permit requests from the Gateway for the OAuth2 authorization code flow. However, there’s no need to configure CORS on the Authorization Server for the Resource Servers, as the Gateway will handle CORS for them.

Add Springdoc OpenAPI to Spring Cloud Gateway

To enable Springdoc OpenAPI in a Spring Boot application, we need to edit files below:

  • build.gradle.kts
  • application.yml
  • OpenAPIConfig.java for setting up @OpenAPIDefinition and @SecuritySchemes
  • Controllers for setting up @SecurityRequirement to the API endpoints
  • SecurityConfig.java for setting up CORS on the Authorization Server and Resource Server
  • Update oauth2_registered_client table to Add redirect_uri

build.gradle.kts

In this example, I’m using an MVC-based Spring Cloud Gateway with Virtual Threads. Therefore, I need to add the springdoc-openapi-starter-webmvc-ui dependency to the build.gradle.kts file.

build.gradle.kts of Spring Cloud Gateway

plugins {
    java
    id("org.springframework.boot") version "3.4.1"
    id("io.spring.dependency-management") version "1.1.7"
    id("org.graalvm.buildtools.native") version "0.10.4"
}

dependencies {
    // omit other dependencies

    // Spring Cloud Gateway MVC
    // (1)
    implementation("org.springframework.cloud:spring-cloud-starter-gateway-mvc")

    // Springdoc OpenAPI
    // (2)
    implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
}        

  1. Thanks to Virtual Threads since Java 21, Spring Cloud Gateway MVC is available as a separate dependency. We need to add this dependency to use Spring Cloud Gateway MVC.
  2. Springdoc OpenAPI is a library that simplifies the generation of OpenAPI (Swagger) documentation for Spring Boot applications. We need to add this dependency to use Springdoc OpenAPI.

If you’re using a Reactive Gateway, you’ll need to include the following dependency:

dependencies {
    // omit other dependencies

    // Spring Cloud Gateway WebFlux
    implementation("org.springframework.cloud:spring-cloud-starter-gateway")

    // Springdoc OpenAPI
    implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.7.0")
}        

application.yaml of Spring Cloud Gateway

application.yaml of Spring Cloud Gateway

spring:
  cloud:
    gateway:
      mvc:
        enabled: true

        # (1)
        routes:
          - id: resource-server
            uri: ${NSA2_RESOURCE_SERVER_URI:https://nsa2-resource-server-example:8082}
            predicates:
              - Path=/resource-server/**
            filters:
              - StripPrefix=1
              - AddRequestHeader=Origin, https://nsa2-gateway:8080
              - TokenRelay=

springdoc:
  cache:
    disabled: true
  api-docs:
    enabled: true
#    path: /v3/api-docs
  oAuthFlow:
    authorizationUrl: ${NSA2_OAUTH_AUTHORIZATION_URL:https://nsa2-auth-server:9000/oauth2/authorize}
    tokenUrl: ${NSA2_OAUTH_TOKEN_URL:https://nsa2-auth-server:9000/oauth2/token}
  swagger-ui:
    oauth:
      client-id: ${NSA2_OAUTH_CLIENT_ID:nsa2}
      client-secret: ${NSA2_OAUTH_CLIENT_SECRET:secret}
    enabled: true
    path: /swagger-ui.html
    config-url: /v3/api-docs/swagger-config
    # Add the following line to enable the Swagger UI to work with OAuth2
    # (2)
    urls:
      - name: gateway-service
        url: /v3/api-docs
      - name: resource-server
        url: /resource-server/v3/api-docs        

  1. Configures Spring Cloud Gateway routes. In this example, we have a route for the resource server. All requests to the /resource-server/** path will be forwarded to the resource server.
  2. Aggregates OpenAPI (Swagger) documentation into Spring Cloud Gateway using its URL path that is defined

The configuration above enables Springdoc OpenAPI in Spring Cloud Gateway which is almost the same with the one that we used in the previous article. The only difference is that we added the urls configuration to the swagger-ui configuration. This configuration is required to make the Swagger UI work with OAuth2. In this example, we have two services: gateway-service and resource-server. We need to define the URL path of the OpenAPI (Swagger) documentation for each service.

OpenAPIConfig.java

OpenAPIConfig.java file is used to set up @OpenAPIDefinition and @SecuritySchemes.

OpenAPIConfig.java of Spring Cloud Gateway

@OpenAPIDefinition(
        info = @io.swagger.v3.oas.annotations.info.Info(
                title = "API Gateway Service",
                version = "1.0.0",
                description = "API Gateway Service"
        )
)
@SecuritySchemes({
    @SecurityScheme(
        name="security-auth",
        type= SecuritySchemeType.OAUTH2,
        flows= @OAuthFlows(
            authorizationCode = @OAuthFlow(
                authorizationUrl = "${springdoc.oAuthFlow.authorizationUrl}",
                tokenUrl = "${springdoc.oAuthFlow.tokenUrl}",
                scopes = {
                    @OAuthScope(name = "openid", description = "OpenID"),
                    @OAuthScope(name = "profile", description = "Profile")
                }
            )
        )
    ),
    @SecurityScheme(
        name="bearer-key",
        type = SecuritySchemeType.HTTP,
        scheme = "bearer",
        bearerFormat = "JWT"
    )
})
public class OpenAPIConfig {
}        

SecurityConfig.java files for CORS Configuration

In Spring Security, CORS configuration can be done by extending the WebMvcConfigurer class and overriding the addCorsMappings(CorsRegistry registry) method. But, in this example, we will use the WebSecurityConfigurerAdapter class to set up CORS configuration.

SecurityConfig.java of Authorization Server

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@Slf4j
public class SecurityConfig {
    private static final List<String> ALLOWED_HEADERS =
            List.of("*");
    private static final List<String> ALLOWED_METHODS =
            List.of("POST", "GET", "PUT", "OPTIONS", "DELETE", "PATCH");

    // (1)
    private static final List<String> ALLOWED_ALL =
            List.of("https://nsa2-gateway:8080", "https://127.0.0.1:8080");

    private static String[] allowedUris = new String[] {
                "/error",
                "/actuator/health",
                "/actuator/health/liveness",
                "/actuator/health/readiness",
                "/.well-known/**"
        };

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(ALLOWED_ALL);
        configuration.setAllowedMethods(ALLOWED_METHODS);
        configuration.setAllowedHeaders(ALLOWED_HEADERS);
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }


    @Bean
    SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            CorsConfigurationSource corsConfigurationSource) throws Exception {

        return http
                // (2)
                .cors(cors -> cors.configurationSource(corsConfigurationSource))
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests.requestMatchers(allowedUris).permitAll()
                                .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults())
                .build();
    }

    // omit other configurations
}        

  1. Defines the allowed origins for CORS. In this example, we allow requests from https://nsa2-gateway:8080 which is the Spring Cloud Gateway.
  2. Configures CORS on the Authorization Server using the cors method.

As for CORS configuration, we need to configure the Resource Server as well. The configuration is almost the same as the Authorization Server.

SecurityConfig.java of Resource Server

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
    private static final List<String> ALLOWED_HEADERS =
                List.of("*");
    private static final List<String> ALLOWED_METHODS =
            List.of("POST", "GET", "PUT", "OPTIONS", "DELETE", "PATCH");
    (1)
    private static final List<String> ALLOWED_ALL =
            List.of("https://nsa2-gateway:8080", "https://127.0.0.1:8080");


    private static final String[] ALLOWED_URIS = {
            "/actuator/health",
            "/actuator/health/liveness",
            "/actuator/health/readiness",
            "/openapi/**",
            "/swagger-ui/**",
            "/swagger-ui.html",
            "/webjars/**",
            "/v3/api-docs/**",
    };
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(ALLOWED_ALL);
        configuration.setAllowedMethods(ALLOWED_METHODS);
        configuration.setAllowedHeaders(ALLOWED_HEADERS);
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain resourceServerSecurityFilterChain(
            HttpSecurity http,
            CorsConfigurationSource corsConfigurationSource,
            JwtAuthenticationConverter nsa2AuthenticationConverter) throws Exception {

        http
            //.cors(Customizer.withDefaults())
            // (2)
            .cors(cors -> {
                cors.configurationSource(corsConfigurationSource);
            })
            .authorizeHttpRequests(authorize ->
                    authorize
                            .requestMatchers(ALLOWED_URIS).permitAll()
                            .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2ResourceServer ->
                    oauth2ResourceServer.jwt(jwt -> {
                        jwt.jwtAuthenticationConverter(nsa2AuthenticationConverter);
                    })
            );

        return http.build();
    }

    // omit other configurations
}        

  1. Defines the allowed origins for CORS. In this example, we allow requests from https://nsa2-gateway:8080 which is the Spring Cloud Gateway.
  2. Configures CORS on the Resource Server using the cors method.

Update oauth2_registered_client table to Add redirect_uri

Now that we aggregated the OpenAPI (Swagger) documentation into Spring Cloud Gateway, we can access the Swagger UI from the Gateway. So the authentication process will be done through the Gateway. So, we don’t need to add redirect urls for the Resource Servers. But, we need to add the redirect_uri for the Gateway to the oauth2_registered_client table.

And make sure to add the 'client_secret_post' to the client_authentication_methods column.

Here is the SQL query to update the oauth2_registered_client table:

UPDATE oauth2_registered_client
SET redirect_uri = 'https://nsa2-gateway:8080/login/oauth2/code/nsa2,https://nsa2-gateway:8080/swagger-ui/oauth2-redirect.htmll',
    client_authentication_methods='client_secret_basic,client_secret_post'
WHERE client_id = 'nsa2';        

Try it out with Swagger UI

Navigate to the Swagger UI URL of the Gateway service. In this example, the URL is https://nsa2-gateway:8080/swagger-ui.html.

Aggregated OpenAPI (Swagger) Documentation

For all the API endpoints of the resource servers(microserices) that are registered in the Gateway, you can see the OpenAPI (Swagger) documentation in the Swagger UI.


Figure 1. Aggregated OpenAPI Documentation in Swagger UI of Gateway

For demonstration purposes, I added OpenAPI documentation for the Gateway service as well. Unlike resource servers, the Gateway services do not use Bearer tokens for authentication. Instead, they use Session Cookies for authentication suggested by BFF (Backend for Frontend) pattern.

And we can see Gateway uses a different dependencies from resource servers.

build.gradle.kts of Gateway

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
    // omit other dependencies
}        

build.gradle.kts of Resource Server

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
    // omit other dependencies
}        

Execute Endpoints of the Resource Servers

As we could see in the previous article, we need to authenticate with the Authorization Server first. And then we can execute the endpoints of the Resource Servers.


Figure 2. Aggregated Resource Server Call in Swagger UI of Gateway

After clicking the lock icon in the top right corner of the Swagger UI, we can authenticate with the Authorization Server. And then we can execute the endpoints of the Resource Servers.

Once you click 'Execute' button, you will see the JWT token in curl command. And you can see the response of the endpoint.

And we can as many resource servers as we want in the Gateway service. They will work the same way as the above example.

Execute Endpoints of the Gateway Service

Unlike the Resource Servers, the Gateway service uses Session Cookies for authentication. So, we can get a different result if we try it in the same way as the Resource Servers.

Let’s see what happens when we try to execute the endpoints of the Gateway service.


Figure 3. Aggregated Gateway Service Call in Swagger UI of Gateway

After clicking the lock icon in the top right corner of the Swagger UI, we can authenticate with the Authorization Server. And then we can execute the endpoints of the Gateway service.

When we click the 'Execute' button, we will see the JWT token in the curl command. However, this time it will return HTML page as its response which is the login page of the Authorization Server. This is because the Gateway service uses Session Cookies for authentication, but we don’t have a session yet.

The simple way to resolve this issue is to call any endpoint of the Gateway service in the browser. Then, the page will be redirected to the login page of the Authorization Server. After logging in, we can get the response of the endpoint on the web browser with the Session Cookies as response headers.

Let’s see what happens when we try to execute the endpoints of the Gateway service in the browser.

Put the URL of the Gateway service in the address bar of your browser. In this example, the URL is https://nsa2-gateway:8080/hello.

Because we don’t have a session yet, the page will be redirected to the login page of the Authorization Server.


Figure 4. Redirected to the Login Page of the Authorization Server

After logging in, we can see the response of the endpoint on the web browser with the Session Cookies as response headers.


Figure 5. Get Response with Session Cookies

In the same web browser, when we open the Swagger UI of the Gateway service, we can execute the endpoints of the Gateway service without any issues.


Figure 6. Gateway Service Call with Session

We can see that there is no JWT token in the curl command. But, the response is the same as the one we got in the web browser.

Conclusion

In this article, we have seen how to aggregate OpenAPI (Swagger) documentation into Spring Cloud Gateway using Springdoc OpenAPI. We have also seen how to configure CORS on the Authorization Server and Resource Server. We have updated the oauth2_registered_client table to add the redirect_uri for the Gateway. And we have tried out the Swagger UI with JWT tokens.

All my LinkedIn articles are available at All My LinkedIn Articles.

要查看或添加评论,请登录

Young Gyu Kim的更多文章

社区洞察

其他会员也浏览了