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:
…
ext {
set('springCloudVersion', "2024.0.0")
set('extensionsVersion', "1.0.0")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "com.vmware.tanzu.springcloudgateway.extensions:extensions-bom:${extensionsVersion}"
}
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:access-control'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:custom'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:graphql'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:sso'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:traffic-control'
implementation 'com.vmware.tanzu.springcloudgateway.extensions:transformation'
}
Or maven pom.xml:
...
<properties>
<java.version>17</java.version>
<spring-cloud.version>2024.0.0</spring-cloud.version>
<scg-extensions.version>1.0.0</scg-extensions.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>extensions-bom</artifactId>
<version>${scg-extensions.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>access-control</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>custom</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>graphql</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>sso</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>traffic-control</artifactId>
</dependency>
<dependency>
<groupId>com.vmware.tanzu.springcloudgateway.extensions</groupId>
<artifactId>transformation</artifactId>
</dependency>
</dependencies>
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 Header | Value |
Cache-Control | no-cache, no-store, max-age=0, must-revalidate |
Pragma | no-cache |
X-Content-Type-Options | nosniff |
Strict-Transport-Security | max-age=631138519 |
X-Frame-Options | DENY |
X-XSS-Protection | 1; 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:
- http://localhost:8080/api/get – with no added header, as expected
- http://localhost:8080/api/anything – with the added header X-Request-Color, as expected
- http://localhost:8080/api/anything/v2 – with the added header X-Request-Color instead of X-Request-Color-V2
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.