If you’re a developer who, like myself, loves Microservices for their flexibility and scalability then you’ve probably run into this challenge:

How can you easily scale your application while maintaining the security and efficiency of service-to-service communications?

Microservices consist of many independent processes communicating with each other over an API. The keyword there is many. All of these processes need to exchange information to perform complex tasks, and each communication exposes your application to vulnerabilities and latency.

In this post, I’m going to show you how to secure service-to-service communications using OAuth and JWTs. We’ll use a Spring Boot app consisting of two Stormpath-backed services. In this example, you authenticate to the first service, which calls the second service to get a response.

Then, we’ll speed things up by reducing the number of calls over the wire with a distributed cache. Caching FTW!

Jot This Down: Use JWTs for property packed tokens

At the heart of the service-to-service communication are JWTs (pronounced “jots”). JWT stands for JSON Web Token and is an open source standard. The Stormpath Java SDK makes use of the open source JJWT library for anything that requires a token. This is everything from our outward facing OAuth2 integration, to our own SSO service called ID Site.

JWTs can function purely as tokens. That is, a string of letters and numbers with no inherent meaning that services make use of. For instance, in an OAuth authorization workflow, the server will create an access token and store it on the Account for future lookup. End users will present the token to identify themselves and the server will do a lookup to confirm that it matches what is has on file.

However, JWTs are so much more. This is because there can be meaningful information encoded into a JWT. This information is called “claims”. There are a number of default claims, including issuer, subject and others. The standard supports custom claims – meaning, you can encode any type of text based key/value pair you want into a JWT. This enables us to work with tokens in a very powerful way. For instance, a JWT may carry information on what level of access a user has to your application.

Most importantly, JWTs can be digitally signed. This makes it possible to ward against man in the middle attacks as you can verify a JWT in code using standard signing and verification algorithms. To be clear, a successful MITM attack would be able to make use of the JWT as a bearer token – not a good thing. However, the attacker would still not be able to alter the JWT in any way due to the signature. Any traffic making use of JWTs should be secured with TLS.

It’s also worth noting the JWTs can be encrypted. This is a step beyond signing. Today, most JWTs are signed, but are not encrypted. This kind of JWT can be easily decoded and, as such, you would never have sensitive information in a JWT of this type. With encryption, new possibilities for passing sensitive data within a JWT arise. Upcoming releases of the JJWT library will include support for encryption.

An exhaustive look at JWTs is beyond the scope of this post. However, this blog post goes into a lot of detail.

Secure Microservice Calls Between Friends

We want to be absolutely sure that our two Microservices can trust each other. JWTs to the rescue!

By using a shared key to sign the JWT that we pass from one service to the other, we can be confident in trusting the JWT by verifying the signature. We can pack whatever information we want to communicate from one service to the other right into the JWT.

Building JWTs with the Java Fluent Interface

Let’s break this down, line by line.

The JJWT library provides a fluent interface for building JWTs.

Line 1 – and each succesive method call – returns a JwtBuilder object.

Lines 2 – 5 put information into the JWT using the standard claims, subject, issued at, expiration and id. These claims have short names that are specified in the standard. These are sub, iat, exp, and id. The subject is the key bit of information we want to send to the second service. It identifies the Account of the authenticated user by href.

Line 7 is crucial to the way this all hangs together securely. We need a shared key so that both Microservices can verify the authenticity of the JWT that’s passed in. The Stormpath Client already has access to its API Key Secret. The combination of the API Key ID and the API Key Secret are what allows your application to securely communicate with Stormpath. We can reuse the API Key Secret as the shared secret between the services.

Lines 8 – 10 sign the JWT using the Stormpath Client’s API Key Secret. The call to compact returns the final encoded String representation of the JWT. This is what we will pass across the wire from one service to the other.

A Look at the Guts of a JSON Web Token

Here’s an example of what the above code produces (newlines added for clarity):

The middle string is the payload. Decoded, the payload looks like this:

Microservices In Action

Let’s take a look at our two services working and then we’ll look at some more of the code.

Stormpath Admin Console Configuration

In order for the example code to work, you’ll need to configure your Stormpath Application and create some Accounts. First, create a Stormpath account here.

In your Stormpath Admin Console, do the following:

  1. Create an Application and make note of the href
  2. Create a Directory and map it to the application
  3. Create a Group in the Directory and make note of the href
  4. Create a number of Accounts and add at least one to the Group from the previous step

For this example, I’ve created these accounts:

  1. [email protected]
  2. [email protected]
  3. [email protected]
  4. [email protected]
  5. [email protected]

And, I’ve added [email protected] to the admin group.

Here’s what it looks like in the admin console:

Application:

application

Admin Group:

group

Accounts:

accounts

Admin Group Accounts:

group_accounts

Microservices Launchpad – Right from Your Commandline

Now, let’s fire up our Microservices and see it in action! Note: There is one application codebase, but we are going to start two servers running on different ports. One of the servers will connect to the other as you will see below.

  1. Build the application:

  2. Start the first server (default port 8080):

  3. Start the second server (port 8081):

We’re going to interact with the first server right from the command line using curl. We’ll authenticate using a standard OAuth2 workflow called grant_type=password. For more information on OAuth, checkout this excellent blog post. We even have a podcast devoted to the topic!

First, we’ll get an access token.

Notice that I am passing in the username and password. In a real production environment, you would want this to be an https connection. In fact, Stormpath insists on it!

Here’s what the response looks like:

Look familiar? Yup – the access_token is a JWT.

Next, as part of the OAuth flow, we will use the access token we got in the previous step as a Bearer token for our service.

And, here’s the response:

Maybe not much to look at, but behind the scenes, we’ve achieved service-to-service communication! Huzzah!

Before we look deeper into the code, let’s look at what happens if we try the same thing with an account that’s not an admin. As a convenience, the code repo has a bash script that asks for your email and password and does the two curl commands for you.

The system works!

Code Walkthrough: Combining Spring Boot, OAuth & JWT

In the pom.xml file, there are three primary dependencies: The Stormpath Java SDK, the JJWT library and the Apache HttpClient library. We use the HttpClient library to connect from one service to the other.

Here’s the first service endpoint from MicroServiceController.java:

Lines 4 – 6 are making sure that the incoming request is from an authenticated user. The Bearer token in the request ensures this.

Lines 11 – 20 we saw earlier. That’s there we are creating our signed JWT.

Line 22 calls a Spring @Service that makes the request of our second server.

Here’s what that service looks like. The file is CommunicationService.java:

Lines 5 – 16 make use of the Apache HttpClient to make an HTTP request to our other server.

Lines 18 and 19 take advantage of the Jackson JSON serialization/deserialization features built into Spring. It’s taking the raw text String returned from our second server and deserializing that into the AccountsResponse object.

Here’s the second service endpoint from MicroServiceController.java:

Lines 9 – 11 verify the JWT using the API Key Secret from the Stormpath Client. Notice that we are again using the Stormpath Client API Key Secret. It’s our shared secret. If the signature on the JWT is not valid, an Exception is thrown when parseClaims is called.

Line 17 calls a Spring @Service that builds our response.

Here’s AdminService.java:

Line 15 ensures that the Stormpath Account passed in is a member of the admin Group. If so, then we build a list of email addresses. If not, then we set the status to ERROR.

The important thing is that an AccountsResponse object is always returned. This makes for a consistent API.

Distributed Cache: Spring Boot Makes It Pluggable

Now that we have our services speaking to each other, let’s see what optimizations we can make.

The Stormpath Java SDK comes with a configurable cache out of the box. That cache is really only designed to be used within a single JVM.

The cache subsystem is completely pluggable and, in conjunction with the Spring Boot integration, it’s super easy.

Let’s look at the network interaction with no distributed cache.

I’ve enabled wire logging on the two application servers. What this means is that every interaction over the network will be shown in the log in detail.

Running through the same example as before, we can see that both the first and second application servers are interacting with Stormpath over the Internet.

8080_wire

8081_wire

Now, let’s enable a distributed cache. For this example, we’ll use Hazelcast. Note: Stormpath is completely cache agnostic. Hazelcast, Redis and others are easy to drop in. The only requirement for a Spring or Spring Boot application is that the cache system supports the CacheManager interface.
In the application.properties, we’ll change:

to

To understand how this property is used, let’s take a look at the CacheConfig.java file:

This takes advantage of some of the great Spring Boot magic, which is all about automatic configuration.

Line 1 identifies this class as a Configuration to Spring.

Line 2 is where the stormpath.hazelcast.enabled property comes into play. The Spring Boot @ConditionalOnProperty annotation ensures that this configuration will only be loaded if the stormpath.hazelcast.enabled is set to true.

Let’s look at this in action. As you can see below, Hazelcast automatically discovers nodes running on the same network. Each Tomcat instance fires up a Hazelcast node and then the nodes connect to each other.

hazelcast_8080

hazelcast_8081

Retrieving the Account in the first server triggers an interaction with Stormpath as expected.

Now, let’s see what happens when the Account object is retrieved in the second service this time:

hazelcast_8081_cache

This time, the Account comes from the cache, which Hazelcast has automatically distributed around to its nodes.

Using a distributed cache in conjunction service-to-service communication minimizes the number of round trips to Stormpath over the Internet.

Exception Handling for Consistent APIs

We want our Microservice to return an AccountsResponse object – no matter what. So, what happens if there’s an Exception? Spring Boot makes it easy to catch Exceptions and return whatever you want.

The @ExceptionHandler annotation is the hook that causes Spring Boot to associate the named Exception (UnauthorizedException in this case) with the method.

When an UnauthorizedException is thrown, the unauthorized method is entered. An AccountsResponse object is created and returned. And, it’s this object that will be returned from the original method.

Recap

Well, it’s been quite a journey!

In this post, we’ve explored how to:

  1. Enable communication between two Microservices
  2. Secure that communicaton using signed JWTs
  3. Optimize the traffic that goes over the wire using a distributed cache
  4. Handle Exceptions using Spring Boot’s @ExceptionHandler annotation

Feel free to drop a line over to email anytime.

Like what you see? to keep up with the latest releases.