A Trip Through Spring Cloud Gateway
API Gateways in Spring
Audience
This article is aimed at developers wanting to learn more about Spring Cloud Gateway. Specifically we will be using it to extract and transform data from API request headers, but the overall techniques are recyclable for a number of purposes.
It will assume a small amount of knowledge of HTTP, JWT tokens and Spring, but the overall gist should be accessible to most.
Argument
Before we begin in earnest, what is an API Gateway?
API Gateways and Spring Gateway
Typically an API Gateway acts as a reverse proxy, sitting between a client and a set of services, offering cross-cutting concerns. This may be authorising the client, forwarding requests to the appropriate service, or adding supplementary information. The client makes requests to the API Gateway, which in turn makes an appropriate set of requests to other services.
Spring Gateway then comprises of three core parts: routes, predicates and filters. Below is a short introduction to each, we will expand on them in turn.
- Route: These are the core part of gateway. They have an Id, a URI representing where requests should be forwarded, predicates and filters. We match a route if the predicate is true.
- Predicate: This lets you match on parts of an HTTP request. Perhaps headers or parameters.
- Filter: These are used to modify requests and responses before or after forwarding your request.
The components then fit together as in the below diagram.
A client makes a request to the gateway. If this request matches a route then it’s sent to the web handler. This handler pushes the request through a request-specific filter chain and then out to the destination service. Note how the filters are split in two, this represents the filtering happening both on the request and the response.
Conceptually this covers how Spring Gateway works, however I appreciate this seems very abstract. Let’s clarify with an example.
Setting Up Our Example
First of all we need to go to the Spring Initializr and start up a new project. I’ve used the dependencies below.
We will also be using the auth0
library for dealing with JWT tokens, so we will need to add the below dependency to our project.
Now we have our Spring project generated we will introduce our first route predicate. This can be done by adding the below configuration in the application.yml
file.
This will match all requests to the v1/users
path with a POST
request and a cookie called user_token
which will contain a JWT token. We are going to use this token to validate the user with our downhill TokenValidation
filtering, specified using the filters option.
If we were so inclined we could use regex to make sure the contents of the cookie was always a JWT token, but as we will be validating later it makes sense to leave it as any string.
Although we won’t cover it in this blog post, we could introduce our own, custom predicate if we wanted to. Perhaps we would like to confirm the request body contained a certain value, in which case we would look into extending the AbstractRoutePredicateFactory
.
Let’s move on to implement our token validation. To aid this I’ll offer a quick refresher on JWT tokens (taken from my article here).
What are JWT Tokens?
JWT Tokens are encoded, cryptographically signed JSON data. The idea is that this is JSON guaranteed to come from a certain source.
So what do these tokens look like? An example JWT token is explained below.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
This essentially consists of three parts, separated by a .
The header: This comprises is the algorithm used to sign the JSON, and the type of token (JWT in our case).
{
"alg": "HS256",
"typ": "JWT"
}
The payload: This contains the ‘claims’. Claims are statements about an entity, as well as some additional data. An example payload is found below.
{
"sub": "123456789",
"name": "John Smith",
"iat": 1516239021
}
The signature: To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and combine them.
signingAlgorithm(
base64UrlEncodedHeader + "." +
base64UrlEncodedPayload,
secret)
This signature can then be used to verify the message hasn’t changed, and if the secret is a private key, we can also use it to verify the sender!
An important point to note is that these tokens are still visible to everyone, we can just guarantee they haven’t been tampered with. So don’t put anything sensitive in them!
Implementing Token Validation in Spring Gateway
To implement our token validation we will create a custom filter. This filter will take in requests, look for a given cookie, extract the token from it, validate it, then use information from the token to add a header to the request. All of it can be done in a single file, contained below.
Let’s try it out! We need to first generate a valid JWT token, I used the site here. If you read the code you’ll notice we need the token to be signed using the secret our-secret
, and we’re expecting a claim userId
. This can be set up in the token as below.
I used Postman to attach the cookie and send on a forward request (you’ll need a downstream service to send to, in our example it was https://backend-services.com/v1/users
). You’ll notice in your backend service you receive the new x-user-id
header!
Conclusion
In conclusion we have demonstrated how Spring Gateway can be used as an API Gateway, with a worked example for authorisation using a JWT token.