Spring

Getting started with Spring Cloud Gateway Extensions

Introduction

Spring Cloud Gateway is a battle-tested, production-proven API gateway that delivers scalable, secure, and high-performance traffic management for modern applications. With the Spring Cloud Gateway Extensions, a part of Tanzu Spring’s enterprise offering, you can now make use of commercial features on top of your open-source Spring Cloud Gateway based applications.

Spring Cloud Gateway (SCG) Extensions consist of a BOM and six independent libraries:

  • access-control – for restricting requests based on access
  • traffic-control – for restricting on request parameters and handling failures
  • sso – add Single Sign-On, restricts traffic based on roles or scopes
  • transformation – for modifying the downstream response
  • graphql – for protecting your GraphQL APIs
  • custom – for building custom filters

A detailed breakdown of the functionality is shown below and at our documentation:

We’ll be making use of some of these features in our example below. But first, let’s create a project!

Starting a new project

To begin using the SCG Extensions into a project, we can begin our journey at https://start.spring.io/. Both gradle and maven build systems are supported. We only need to add one dependency – the Reactive Gateway. Note: the servlet-based Gateway is not currently supported.

We’ll also need access to the Spring Enterprise Artifactory. Follow the instructions at Spring Enterprise Subscription for Application Developers to configure your project.

Now let’s add the SCG Extensions to the project. By making use of the SCG Extensions BOM, we can manage a single consistent version across the extension libraries. For completeness, all the libraries are added in the example below, but you can pick and choose to add only what you need.

In your build.gradle:

Or maven pom.xml:

Now that we’ve added the extension libraries, let’s see them in action!

Single Sign-On

The sso library can be used to add Single Sign-On to your applications, while also restricting requests based on roles. In this example, we’ll use the Tanzu Local Authorization Server, also a part of Tanzu Spring.

First, download the jar. Then in a tanzu-local-auth-server-config.yml, provide the configuration:

tanzu:
  local-authorization-server:
    users:
      - username: alice
        password: alice
        attributes:
          roles:
            - viewer
      - username: bob
        password: bob
        attributes:
          roles:
            - editor
    clients:
      - client-id: test
        client-secret: test
        client-authentication-methods:
          - client_secret_basic
        authorization-grant-types:
          - authorization_code
          - refresh_token
        redirect-uris:
          - http://localhost:8080
        require-consent: "true"
        scope:
          - openid
          - profile
          - roles

Here we added two users:

  • alice, with the “viewer” role
  • bob, with the “editor” role

Start the local authorization server, running on http://localhost:9090:

java -jar tanzu-local-authorization-server.jar --config=tanzu-local-auth-server-config.yml

Then in the gateway’s application.yaml, set:

spring:
  cloud:
    gateway:
      routes:
        - uri: https://httpbingo.org
          predicates:
            - Path=/viewer/**
          filters:
            - SsoLogin
            - StripPrefix=1
            - Roles=viewer,editor
        - uri: https://httpbingo.org
          predicates:
            - Path=/editor/**
          filters:
            - SsoLogin
            - StripPrefix=1
            - Roles=editor
  security:
    oauth2:
      client:
        registration:
          sso:
            provider: sso
            client-id: test
            client-secret: test
            authorization-grant-type: authorization_code
            scope: openid,profile,roles
        provider:
          sso:
            issuer-uri: http://localhost:9000

We have two routes here, both which uses the SsoLogin and Roles filters:

  • /viewer/** – accessible with either viewer or editor roles
  • /editor/** – accessible with the editor role

Start the gateway and in an incognito tab, open http://localhost:8080/viewer. Notice it redirects to the SSO url, http://localhost:9000/login. After we log in with username/password: alice/alice, we are redirected back to the gateway, which returns a 200. However, trying to access http://localhost:8080/editor returns a 403 Access Denied. 

Trigger the logout by accessing http://localhost:8080/scg-logout. Now try accessing  http://localhost:8080/editor again, this time logging in with username/password: bob/bob. Since this user has the “editor” role, the gateway returns a 200. The viewer url, http://localhost:8080/viewer, is also accessible as bob since this route allows for either the “viewer” or “editor” roles.

Note: To implement Single Sign-On with high availability, a ReactiveSessionRepository implementation must be provided. Additional code and/or dependencies may be required.

With the sso library, Single Sign-On can be configured once at the gateway level, instead of having different implementations for each backend system. To learn about more features in the sso dependency, visit the library’s documentation.

Access Control

In this section, let’s take a look at one of the access-control library’s features – the custom SecureHeaders global filter. This filter is automatically activated and adds the following security best-practice headers to downstream responses:

Activated Secure HeaderValue
Cache-Controlno-cache, no-store, max-age=0, must-revalidate
Pragmano-cache
X-Content-Type-Optionsnosniff
Strict-Transport-Securitymax-age=631138519
X-Frame-OptionsDENY
X-XSS-Protection1; mode=block

To see this in action, configure a route in your application.yaml

spring:
  cloud:
    gateway:
      routes:
        - uri: https://httpbingo.org
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1

Start the gateway and run the following curl command:

curl -i http://localhost:8080/api/json

You should see the extra headers returned:

HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-type: application/json; charset=utf-8
date: Mon, 24 Feb 2025 22:23:26 GMT
content-length: 421
server: Fly/01acac8f2 (2025-02-24)
via: 1.1 fly.io
fly-request-id: 01JMX0XP96Z67SM0PDGFZM4KD0-iad
X-Frame-Options: DENY
Strict-Transport-Security: max-age=631138519
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
X-Content-Type-Options: nosniff
X-Xss-Protection: 1 ; mode=block
Pragma: no-cache
Expires: 0

{ response... }

The  SecureHeaders is applied globally, making it easy to apply security best practices across all your routes.

Note: This behavior can be de-activated globally by setting the following environment variable:

tanzu:
  api-gateway:
    secure-headers:
      deactivated: true

To learn about more features in the access-control dependency, visit the library’s documentation.

Traffic Control

In this section, we’ll take a look at the Path Predicate Route Specificity feature provided in the traffic-control library. This capability, configured via an environment variable, changes how the gateway resolves Path predicates. To illustrate the scenario, let’s define an application.yaml:

spring:
  cloud:
    gateway:
      routes:
        - uri: https://httpbingo.org
          predicates:
            - Path=/api/anything/**
          filters:
            - AddRequestHeader=X-Request-Color, blue
            - StripPrefix=1
        - uri: https://httpbingo.org
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1
        - uri: https://httpbingo.org
          predicates:
            - Path=/api/anything/v2/**
          filters:
            - AddRequestHeader=X-Request-Color-V2, blue
            - StripPrefix=1

In this example, we have three routes with the following Path predicates:

  • /api/anything/**
  • /api/**
  • /api/anything/v2/**

We’d like our more specific v2 endpoint to use add X-Request-Color-V2 instead of X-Request-Color. When testing the gateway, we see:

The problem on our v2 endpoint can be resolved by setting the order on the route definition to a lower (more negative) value, resulting in a higher priority. Alternatively, we can modify the application yaml to place the more specific Path predicate above the less specific ones. But with several route definitions, sometimes owned by different teams, either of these options can be risky and difficult to apply. 

Instead, what we’d really like is to route to a more specific Path predicate, regardless of where the route definition appears on the list. By setting the following environment variable, we can get this behavior:

tanzu:
  api-gateway:
    path-predicate:
      route-order: path_length

Note: Order is still respected. If a route with less specific Path predicate has a lower order (higher priority) specified, it would be routed there instead.

With the Path Predicate Route Specificity feature, it’s easier to manage more routes across different backends. To learn about more features in the traffic-control dependency, visit the library’s documentation.

Transformation

In this section, we’ll take a look at a couple of filters in the transformation dependency – XmlToJson and JsonToXml. Define an application.yaml:

spring:
  cloud:
    gateway:
      routes:
        - uri: https://httpbingo.org
          predicates:
            - Path=/test1/**
          filters:
            - XmlToJson
            - StripPrefix=1
        - uri: https://httpbingo.org
          predicates:
            - Path=/test2/**
          filters:
            - JsonToXml
            - StripPrefix=1

As their name describes, XmlToJson converts an XML response to JSON, while JsonToXml converts a JSON response to XML. To test the XML conversion, start the app and run on the terminal:

curl http://localhost:8080/test1/xml

You should see a JSON response similar to:

{"slideshow":{"title":"Sample Slide Show","date":"Date of publication","author":"Yours Truly","slide":[{"type":"all","title":"Wake up to WonderWidgets!"},{"type":"all","title":"Overview","item":[{"":["Why "," are great"],"em":"WonderWidgets"},"",{"":["Who "," WonderWidgets"],"em":"buys"}]}]}}

You can view the original XML response via:

curl https://httpbingo.org/xml

Likewise, you can test XML conversion in action with:

curl http://localhost:8080/test2/get

And you can view the original JSON response via:

curl http://httpbingo.org/get

With the  XmlToJson and JsonToXml filters, you can restructure the returned response in the format as needed by your downstream apps. To learn about more features in the transformation dependency, visit the library’s documentation.

GraphQL

The graphql extension provides a few filters to help protect your GraphQL endpoints. Let’s take a look at how the RestrictGraphQLOperationDepth filter can restrict the query depth.

First, define the application.yaml with the Star Wars GraphQL API:

spring:
  cloud:
    gateway:
      routes:
        - uri: https://swapi-graphql.netlify.app
          predicates:
            - Path=/graphql/**
          filters:
            - RestrictGraphQLOperationDepth=3

Run the app and fetch the Star Wars film titles:

echo '{ "query": 
  "{  
    allFilms {
      films {
        title
      }
    }
  }"
}' | tr -d '\n' | curl --location 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--silent --data @- | jq

In this query, we are within our depth limit of 3. You should see a similar response to:

{
  "data": {
    "allFilms": {
      "films": [
        {
          "title": "A New Hope"
        },
        {
          "title": "The Empire Strikes Back"
        },
        {
          "title": "Return of the Jedi"
        },
        {
          "title": "The Phantom Menace"
        },
        {
          "title": "Attack of the Clones"
        },
        {
          "title": "Revenge of the Sith"
        }
      ]
    }
  }
}

Now try to a query with a depth limit of 5:

echo '{ "query":
  "{  
    allFilms {
      films {
        title
        speciesConnection {
          species {
            name
          }
        }
      }
    }
  }"
}' | tr -d '\n' | curl --location 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--silent --data @- | jq

This returns an error:

{
  "errors": [
    "Depth limit exceeded."
  ]
}

By limiting the depth, you can prevent resource-intensive requests from being executed. To learn about more features in the graphql dependency, visit the library’s documentation.

Building your own custom filters

The custom library provides a couple of filters that can be optionally used when building your own custom filters. To learn more about the custom dependency, visit the library’s documentation.

Conclusion

In this article, we covered how to use some of the features in Spring Cloud Gateway Extensions, a part of Tanzu Spring. The different libraries can add single sign-on, access control, traffic control, transformations, and GraphQL capabilities on top of your own open source Spring Cloud Gateway implementation. For an overview of all the features across all the libraries, visit the documentation.