How to Build a Microservices Application Using Spring Boot

How to Build a Microservices Application Using Spring Boot

Now that we've learned how to set up and run a Spring Boot app using Eclipse IDE and CLI, we'll see how to build a microservices application using Spring Boot.

Let me show how we can create Microservices Application for Top Sports Brands using Spring Boot and Netflix Eureka Server in detail. Before creating the application, let me tell you what are the challenges with Microservices Architecture.

Spring Boot enables building production-ready applications quickly and provides non-functional features:

  • Embedded servers which are easy to deploy with the containers.
  • It helps in monitoring the multiples components.
  • It helps in configuring the components externally.

So, let us see the challenges with microservices architecture.

Challenges With Microservice Architecture

While developing a number of smaller microservices might look easy, there is a number of inherent complexities that are associated with microservices architectures. Let's look at some of the challenges:

  • Automating the Components: It becomes difficult to automate everything because there are a number of smaller components instead of a monolith, i.e. builds, deployment, monitoring, etc.
  • Perceptibility: There is a number of small components to deploy and maintain which sometimes becomes difficult to monitor and identify problems. It requires great perceptibility around all the components.
  • Configuration Management: There is a great need to maintain the configurations for the components across the various environments.
  • Debugging: It becomes difficult to probe each and every service for an error. Centralized Logging and Dashboards are essential to make it easy to debug problems.
  • Consistency: You cannot have a wide range of tools solving the same problem. While it is important to foster innovation, it is also important to have some decentralized governance around the languages, platforms, technology and tools used for implementing/deploying/monitoring microservices.
Building Architecture for Top Sports Brands With Spring Boot

In this Spring Boot microservices example, we will be creating Top Sports Brands' application, which will have three services:

  1. Eureka Service- This service will register every microservice and then the client microservice will look up the Eureka server to get a dependent microservice to get the job done. This Eureka Server is owned by Netflix and in this, Spring Cloud offers a declarative way to register and invoke services by Java annotation.
  2. Item Catalog Service - This service will generate the list of sports brands which are popular in the market.
  3. Edge Service - It is similar to the standalone Item service created in Bootiful Development with Spring Boot and Angular. However, it will have fallback capabilities which prevent the client from receiving an HTTP error when the service is not available

Let us see which of the following tools required to create this Spring Boot microservices example application.

If you facing any difficulty in installing and running the above tools, please refer to this blog.

Creating a Eureka Service

To begin with, create a EurekaServer Spring Starter Project in Eclipse IDE. Click on Spring Starter Project and click on Next.

Name your Spring Starter Project as EurekaServer and other Information will be filledautomatically.

Note: Make sure your Internet is connected otherwise it will show an error.

Now, modify EurekaServer/src/main/resources/application.properties file to add a port number and disable registration.

Open EurekaServer/src/main/java/com/example/EurekaServiceApplication.java and add @EnableEurekaServer above @SpringBootApplication.

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer @SpringBootApplication

This annotation will configure a registry that will allow other applications to communicate.

To start the Application: Right Click on the

Project -> Run As -> Click on " Spring Boot App "

http://localhost:8761

Now open http://localhost:8761. Here Spring Eureka Server will open and will show no service will be running.

Spring Boot Microservices: Creating an Item Catalog Service

Again create a new project. Use Item-catalog-service for the artifact name and click on Next.

Add the following dependencies:

  • Actuator: features to help you monitor and manage your application
  • EurekaDiscovery: for service registration
  • JPA: to save/retrieve data
  • H2: an in-memory database
  • RestRepositories: to expose JPA repositories as REST endpoints
  • Web: Spring MVC and embedded Tomcat
  • DevTools: to auto-reload the application when files change
  • Lombok: to reduce boilerplate code

Click on Finish.

Now, create an entity, to ItemCatalogServiceApplication.java . The code below assumes you're putting all classes in the same file.

If you're using an editor that doesn't auto-import classes, here's the list of imports needed at the top of ItemCatalogServiceApplication.java.

Add an application name in item-catalog-service/src/main/resources/application.properties file to display in the Eureka service, and set the port to 8088.

Now, Create the Cloud Properties file.

Click on File -> New -> Other -> File and add the below code in this file and save it.

eureka.instance.hostname=${vcap.application.uris[0]:localhost}
eureka.instance.nonSecurePort=80
eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
eureka.instance.leaseRenewalIntervalInSeconds = 5
eureka.client.region = default

eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=${vcap.services.pwa-eureka-service.credentials.uri}/eureka/

Now, to start the Application:

Right Click on Project -> Run As -> Click on " Spring Boot App "

Note: In case of error try this step: Right Click on the Project -> Run As -> Click on "Maven Build."

Now open http://localhost:8761. Here you will see Item Catalog service will be running.

You will see the list of items from the catalog service.

Now let us move forward and create the Edge Service.

Creating an Edge Service

It is similar to the standalone Item service created in Bootiful Development with Spring Boot and Angular. However, it will have fallback capabilities which prevent the client from receiving an HTTP error when the service is not available.

Again create a new project. Use edge-service for the artifact name:

  • Eureka Discovery: for service registration
  • Feign: a declarative web service client
  • Zuul: provides intelligent routing
  • Rest Repositories: to expose JPA repositories as REST endpoints
  • Web: Spring MVC and embedded Tomcat
  • Hystrix: a circuit breaker to stop cascading failure and enable resilience
  • Lombok: to reduce boilerplate code

Click on Finish.

Since the item-catalog-service is running on port 8088, you'll need to configure this application to run on a different port. Modify edge-service/src/main/resources/application.properties to set the port to 8089 and set an application name.

Now, Create the Cloud Properties file.

Click on File -> New -> Other -> File and add below code in this file and save it.

eureka.instance.hostname=${vcap.application.uris[0]:localhost}
eureka.instance.nonSecurePort=80
eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
eureka.instance.leaseRenewalIntervalInSeconds = 5
eureka.client.region = default

eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=${vcap.services.pwa-eureka-service.credentials.uri}/eureka/

To enable Feign, Hystrix, and registration with the Eureka server, add the appropriate annotations to EdgeServiceApplication.java:

Create a Item DTO (Data Transfer Object) in this same file. Lombok's will generate a methods, getters, setters, and appropriate constructors.

Create a ItemClient interface that uses Feign to communicate to the Item-catalog-service.

Create a RestController below the ItemClient that will filter out less-than-top brands and shows a /top-brands endpoint.

Start the edge-service application with Maven or your IDE and verify it registers successfully with the Eureka server.

Now invoke localhost:8089/top-brands, you will see the list of top brands from the catalog service.

Note: If you shut down the item-catalog-service application, you'll get a 500 internal server error.

To fix this, you can use Hystrix to create a fallback method and tell the goodItems() method to use it.

Restart the edge-service and you should see an empty list returned.

Start the item-catalog-service again and this list should eventually return the full list of top brands names.

If you want the source code for this application, please leave your comment in comment section.

Thank you !

Set up Web App with Spring Boot and Spring Security

Set up Web App with Spring Boot and Spring Security

Download the Spring Boot Web App Example Project. Run the Initial Web App. Add Project Dependencies for Your Spring Boot + Spring Security Web App. Understand Your Spring Boot App. Set Up Okta for OAuth 2.0 Single Sign-On. Configure Your Spring Boot App for Single SignOn (SSO) Refine Our Permissions

Developers know that securing web apps can be a pain. Doing it right is tough. The worst part is that “right” is a moving target. Security protocols change. Vulnerabilities are found in dependencies and patches are released. Tons of often complex boilerplate code has to be generated. The software-as-service paradigm has proliferated over the last decade, and while I love reinventing the wheel as much as the next developer (because, clearly, I’m gonna write it better than the yahoo they hired), security is an area where I’m happy to offload this work to specialists. Enter Okta.

In this tutorial, you’re going to use Spring Boot to build a simple web application with a user registration system and a login system. It will have the following features:

  • Login and registration pages
  • Password reset workflows
  • Restricting access according to group membership
Download the Spring Boot Web App Example Project

The first thing you’re going to need is a free Okta account. If you don’t already have one

The next thing will be to download the example project for this tutorial from GitHub.

git clone https://github.com/oktadeveloper/okta-spring-simple-app-example.git spring-app

This project uses Gradle, as the build tool, and the Thymeleaf templating system.

Run the Initial Web App

Once you have downloaded the example code from the GitHub repository, checkout out the Start tag using the following git command: git checkout tags/Start.

The app at this point it not protected at all. There is no authorization or authentication enabled (even though the necessary dependencies are included in the build.gradle file). Go ahead and run the example by opening a terminal and, from the project root directory, running the command ./gradlew bootRun (The bootRun command is a task provided by the Gradle Spring Boot plugin, added to the build.gradle file in the buildscript section at the top of the file).

Navigate to http://localhost:8080 in your favorite browser, and you should see this:

And if you click on the “Restricted” button:

Add Project Dependencies for Your Spring Boot + Spring Security Web App

The project dependencies are defined in the build.gradle file (see below). There’s a lot going on in this file, and this tutorial isn’t going to try and explain the Gradle build system to you. Feel free to check out their documentation. I just want to point out a few things.

First off, notice that we’re including the okta-spring-boot-starter. This project greatly simplifies integrating Okta with your Spring Boot application. It’s entirely possible to use Okta and Spring Boot without this starter. In fact, up to the point where Groups and Roles are introduced, the differences are minor (mostly involve application.yml changes). However, once you start to trying to integrate Groups and Roles, the Okta Spring Boot Starter saves a lot of coding. If you’d like to look a little deeper, take a look at the Okta Spring Boot Starter GitHub project.

The rest of the dependencies deal with Spring and Spring Boot. You’ll notice none of the org.springframework.bootdependencies have version numbers. This is because of some behind-the-scenes magic being done by the Spring io.spring.dependency-management Gradle plugin. The Spring Boot Version is set by the build script property springBootVersion near the top of the build.gradle file. Based on this version number, the Spring dependency management plugin decides what versions of dependencies to include.

We’re also bringing in the org.springframework.boot Gradle plugin, which adds the bootRun task that we’ll use to run the app.

  • spring-boot-starter-security and spring-boot-starter-web are core Spring Boot dependencies.
  • spring-security-oauth2-autoconfigure is required to use the @EnableOAuth2Sso annotation that we use to hook OAuth and Single Sign-On into our app.
  • spring-boot-starter-thymeleaf and thymeleaf-extras-springsecurity4 bring in the Thymeleaf templating system and integrate it with Spring Security.
buildscript {  
   ext {  
      springBootVersion = '2.0.5.RELEASE'  
  }  
   repositories {  
      mavenCentral()  
   }  
   dependencies {  
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")  
   }  
}  

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.okta.springboot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile('com.okta.spring:okta-spring-boot-starter:0.6.0')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4')
compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.5.RELEASE')
testCompile('org.springframework.boot:spring-boot-starter-test') "
testCompile('org.springframework.security:spring-security-test')
}

/*
This is required to resolve a logging dependency conflict between the
okta-spring-boot-starter and the various spring dependencies.
*/
configurations.all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
exclude group: 'org.springframework.boot', module: 'logback-classic'
}

Understand Your Spring Boot App

The Java web application has only three class files and a few templates. Obviously Spring Boot is doing a lot of heavy hitting going on in the background, but what’s going on in our class files?

The application entry point is in the SpringSimpleApplication class:

@SpringBootApplication
public class SpringSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSimpleApplication.class, args);
}
}

Two important things are happening here that get things rolling: 1) we use the @SpringBootApplication annotation, and 2) our main method calls the SpringApplication.run() method. This is the entry point to the entire Spring/Spring Boot system.

The SpringSecurityWebAppConfig class is a way to use Java code to configure how Spring Boot handles web app security. Here we use the HttpSecurity object to remove authorization from all endpoints. By default, the Spring Boot behavior is the opposite: all endpoints require authorization.

@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll();          
}

}

The @Configuration annotation tells Spring that we are using the class as a source of programmatic configuration, allowing us to override the configure() method.

The last Java class, SimpleAppController, is our only controller object. Controllers in a Spring Boot web application are where URL requests are mapped to Java code. The @Controller annotation tells Spring that this class is a controller.

@Controller
class SimpleAppController {

@RequestMapping("/")  
String home() {  
    return "home";  
}  

@RequestMapping("/restricted")  
String restricted() {  
    return "restricted";  
}  

}

Connections between class methods and URLs are made using the @RequestMapping annotation.

We have two mappings:

  1. “home” mapping
  2. “restricted” mapping

Remember that initially nothing is actually “restricted”, so don’t get confused by that. You’ll lock that mapping down in a bit.

Also notice that the classes return a simple text string, but this is getting auto-magically turned into a full html file. This is part of the Thymeleaf dependency that is included in the build.gradle file. These strings are assumed to be template file names, which are by default paths in the templates directory on the classpath.

Thus “home” is mapped to the src/main/resources/templates/home.html template file. When the web app is packaged in the the final jar, the entire resources folder is copied into the classpath, so that the templates directory is accessible at runtime.

Set Up Okta for OAuth 2.0 Single Sign-On

Now you’re going to set up authorization for our app. Okta makes this super easy. You should have already signed up for a free developer.okta.com account. Now you’re going to create an OpenID Connect (OIDC) application to use with OAuth 2.0 Single Sign-On (SSO).

That might be a lot of jargon and acronyms, if you’re not already familiar with them. Very simply, OAuth 2.0 is an industry standard for authorization - a standardized and tested method by which authorization servers and applications can communicate to facilitate user authorization. OpenID Connect is a layer on top of OAuth 2.0 that standardizes and simplifies the authorization procedure as well as providing user authentication. Together they provide a proven way for an application to interact with a remote server that provides authentication and authorization services (such as Okta).

To create an OIDC app, open your Okta developer dashboard. Click on the Applications top menu item, and then click on Add Application.

You should see the following screen. Click on the icon for the Web option. Click Next.

You need to update a few of the initial configuration options. First change the name to something more descriptive. I used “Okta Spring Boot Simple Web App.” Next update the Login redirect URIs to http://localhost:8080/login. Click Done.

This will take you to the new application’s general configuration tab. Scroll down and note the Client ID and Client secret. You’ll need these later.

That’s all you need to do to set up Okta for OAuth! Now let’s return to the Spring Boot app and hook our new OIDC application into the Spring Boot application.

Configure Your Spring Boot App for Single Sign-On (SSO)

Now you need to configure the Spring Boot app to interact with the Okta servers. This is super easy. We need to do two things:

  1. Add the @EnableOAuth2Sso annotation
  2. Update the application.yml configuration

First add the @EnableOAuth2Sso annotation to the SpringSecurityWebAppConfig class.

@EnableOAuth2Sso
@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests().anyRequest().permitAll();          
}  

}

The @EnableOAuth2Sso annotation does a TON of stuff. It’s worth digging into to understand what’s going on. You can check out Spring’s docs on the annotation itself, and their Spring Boot and OAuth2 tutorial.

One thing I want to point out (bc this has been bugging me a while and I just figured it out) is that you can put this annotation on other classes in the project. However, if you do, be aware that Spring is going to create a WebSecurityConfigurerAdapter and add it to the security chain. Since we’re also creating a WebSecurityConfigurerAdapter, there will be two of them, and you’ll get an error about conflicting chain orders. This is because both WebSecurityConfigurerAdapters will by default use the same chain order. You can resolve this error by adding an @Order(101) annotation to our customized class. However, even better is to add the @EnableOAuth2Sso annotation to our WebSecurityConfigurerAdapter class, WebSecurityConfigurerAdapter, and Spring will use that class instead of creating a duplicate one.

The second change you need to make is update the src/main/resources/application.yml file, filling in some Okta-specific configuration options for the OAuth SSO values take from our Okta OIDC application.

You’ll need to fill in your Client ID and Client secret from the application you created above. You’ll also need to change the issuer URL so that it reflects your Okta preview URL, something like dev-123456.oktapreview.com.

server:
port: 8080

spring:
resources: static-locations: "classpath:/static/"

okta:
oauth2:
issuer: https://{yourOktaDomain}/oauth2/default
clientId: {yourClientId}
clientSecret: {yourClientSecret}
rolesClaim: groups

Refine Our Permissions

Now you’re going to want to update the SpringSecurityWebAppConfig class so that you have a public home page and a restricted “restricted” page. We do this by using Spring’s fluent API for the HttpSecurity object.

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableOAuth2Sso
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests()  
            .antMatchers("/").permitAll() // allow all at home page
            .antMatchers("/img/**").permitAll()  // allow all to access static images
            .anyRequest().authenticated();  // authenticate everything else!
}  

}

Restart your app and now you should be able to:

  1. See the home page without authenticating
  2. NOT see the /restricted page without authenticating
  3. Be able to authenticate using Okta Single Sign-On

This point in the tutorial corresponds to the OktaOAuthSSO tag in the GitHub repository.

Take a Look at the Thymeleaf Templates

The Thymeleaf templates are pretty self explanatory, on the whole, but I did want to point out a couple things. Thymeleaf templates are fully valid HTML5, which is nice. If you want to dig deeper, you can head over to their website and their documentation.

What I wanted to point out is how the template brings in authentication information. To do this, we’re using the thymeleaf-extras-springsecurity plugin. This is included in the build.gradle file with the following line:

compile ("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")

And is included in the template file as an XML namespace attribute on the main <html> tag.

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

This plugin is what allows us to check if a user is authenticated using the th:if attribute with a custom SPEL expression (Spring Expression Language). It also allows us to insert authentication properties. Below you see a span <span th:text="${#authentication.name}"></span> that is used to insert the name of the authenticated user.

<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<!--// <th:block th:include="fragments/head :: head"/> //-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<div class="okta-header">
<img src="img/logo.png"/>
</div>

        &lt;!--/* displayed if account IS NOT null, indicating that the user IS logged in */--&gt;  
        &lt;div th:if="${#authorization.expression('isAuthenticated()')}"&gt;  
            &lt;h1 th:inline="text"&gt;Hello, &lt;span th:text="${#authentication.name}"&gt;&lt;/span&gt;!&lt;/h1&gt;  
            &lt;a href="/restricted" class="btn btn-primary"&gt;Restricted&lt;/a&gt;  
        &lt;/div&gt;  

        &lt;!--/* displayed if account IS null, indicating that the user IS NOT logged in */--&gt;  
        &lt;div th:unless="${#authorization.expression('isAuthenticated()')}"&gt;  
            &lt;h1&gt;Who are you?&lt;/h1&gt;  
            &lt;a href="/restricted" class="btn btn-primary"&gt;Restricted&lt;/a&gt;  
        &lt;/div&gt;  
    &lt;/div&gt;  
&lt;/div&gt;  

</div>
</body>
</html>

The thymeleaf-extras-springsecurity plugin has some other nice features as well. If you want to dig a little deeper, check out the project repository on GitHub.

Secure Access By Group Membership

The next step in our tutorial is to add Group-based authentication using user groups that we’ll create and define on Okta. A very common example of this is to have an “admin” section of a website and a “user” section of a website, along with perhaps a public home page open to everybody. In this example, “admin” and “user” would correspond to two different groups of which an authenticated user could be a member. What we want to do is be able to restrict access to URL endpoints based on user group membership, and to be able to assign users to these groups.

A side note: groups vs roles. What’s the difference?

  • A “group” is a collection of users, and permissions are assigned to the group. Generally speaking group membership is relatively static, at least throughout the duration of a session.
  • A “role” is a set of permissions that a user can inherit when he/she acts under that role. Roles are generally more dynamic in nature. Users can have many roles. Roles frequently are activated or deactivated depending on complex criteria and often may change throughout a user session.

In practice, for simple authorization systems, they’re pretty similar. The main difference is that groups classify based on individual identity, whereas roles classify based on permissible activities. You’ll probably see apps and tutorials on the wild and woolly internet that ignore this difference, as it’s functionally somewhat subtle. (But now you know. And you can get on the comment thread for the tutorial in question and write a comment correcting the author.)

Configure Authorization Groups in Okta

Go to your developer.okta.com dashboard. From the top menu, go to Users and click on Groups.

Click on the Add Group button.

Name the group “Admin” and give it a description (I put “Administrators,” doesn’t matter what you put here really, just something descriptive).

Click on the group Name to open the group and click on the Add Members button. Add your user to the Admin group.

Next add a new user that’s not an admin.

  • Go to Users from the top menu and click on People.
  • Click Add Person.
  • Fill out the popup form:
  • First name: Not
  • Last name: Admin
  • Username: [email protected]
  • No groups or secondary email
  • Password: Set by admin
  • Assign a password
  • Uncheck “User must change password on first login”
  • Click Save

The next thing you’ll need to do is add a “groups” claim to the default authorization server.

  • From the top menu, go to API and click on Authorization Servers”
  • Click on the default authorization server.
  • Click on the Claims tab.
  • Click the Add Claim button.
  • Update the popup form to match the image below
  • Name: groups
  • Token type: Access
  • Value type: Groups
  • Filter: Regex .*
  • Don’t disable
  • Include in any scope

What you’re doing here is telling Okta to include a “groups” claim in the access token that is sent to your application. This is the OAuth method of Okta telling your application about the groups your authenticated user is a member of. Somewhat confusingly, these will be called “authorities” on the Spring application side, which is an abstract term for groups/roles/privileges communicated by the OAuth server to the app.

Now we have two users. Your primary user, which has been added to the Admin group, and a new user that is not in the admin group. We’ve also configured Okta to add the groups claim to the access token. Now all we have to do is make a few changes to the app code!

Update Your Spring Boot + Spring Security App to Use Group-based Authorization

This is where the Okta Spring Boot Starter really starts to shine. Normally if you wanted to map the security groups and groups claims that we are sending in the token to groups in the app, you’d have to write an extractor class or two to handle the extraction, as well as perhaps a group class. The Okta Spring Boot Starter handles all of this for you!

The first thing you’re going to want to do is add the following annotation to your SpringSecurityWebAppConfig class.

@EnableGlobalMethodSecurity(prePostEnabled = true)

Like so:

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@EnableOAuth2Sso
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
/* class contents omitted for brevity */
}

This annotation enables the next annotation that we’re going to use, the @PreAuthorize annotation. This annotation allows us to use a Spring Expression Language (SpEL) predicate to determine if the controller method is authorized. The predicate expression is executed before the app even enters the controller method (hence the “pre”-authorize).

In the SimpleAppController class, add a new method called admin like so:

import org.springframework.security.access.prepost.PreAuthorize;

@Controller
class SimpleAppController {

/* other controllers omitted for clarity */ 

@RequestMapping("/admin")  
@PreAuthorize("hasAuthority('Admin')")  
String admin() {  
    return "admin";  
}  

}

Just to recap a little, this method does the following:

  • create a mapping for the /admin url endpoint;
  • assign the /admin endpoint an authorization scheme based on SpEL;
  • and simply return the name of a Thymeleaf template, assumed to be in the /templates directory (which we’ll create next).

Create the new admin template page. In the src/main/resources/templates directory, create a new file called admin.html with the following contents:

<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<!--// <th:block th:include="fragments/head :: head"/> //-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<div class="okta-header">
<img src="img/logo.png"/>
</div>

        &lt;h1&gt;Welcome to the admin page!&lt;/h1&gt;  

        &lt;a href="/" class="btn btn-primary"&gt;Go Home&lt;/a&gt;  

    &lt;/div&gt;  
&lt;/div&gt;  

</div>
</body>
</html>

You may be asking yourself what the SpEL expression used in the @PreAuthorize annotation means. Why is the SpEL expression hasAuthority and not hasGroup? A correct answer is somewhat complicated, having to do with the fact that Spring calls permissions privileges and authorities in different contexts, which can be mapped to groups and roles in the app. When using Spring Boot and OAuth, an ‘authority’ is often equated with a ‘role’, which is fine. But you said we’re using groups, not roles? Right. Practically speaking, in this instance, it doesn’t matter because Okta knows we’re talking about groups and the app knows we’re talking about groups, and in the middle we just use the groups claim and the authorities fields to communicate the text strings that represent the groups the user is a member of.

A helpful hint:

If you want to inspect the authentication information that the Spring Boot App is receiving, you can add the following line in one of the controller methods before the return statement.

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Set a breakpoint on this line, or right after it, really, and run the app with a debugger that allows you to inspect the authentication object. It’s a great way to learn and debug problems.

Try Out Your New Spring Boot + Spring Security Web App!

That’s pretty much it. You should be able to restart the app and log in with two different users. Only the user that was added to the Admin group should be able to access the admin page. You’ll have to directly navigate to http://localhost:8080/admin (as we didn’t add a link or a button). If you try to navigate to the admin page with the other user, you’ll see the beautiful whitelabel error page showing a 403 / Unauthorized error.

Keep in mind that when switching between users you’ll have to stop the app, log out of your developer.okta.com account, and restart the app. You can also use an incognito window in your browser.

This part of the tutorial corresponds to the GroupsAuth tag, which you can checkout using the following command git checkout tags/GroupsAuth.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ Spring & Hibernate for Beginners (includes Spring Boot)

☞ Spring Framework Master Class - Learn Spring the Modern Way!

☞ Master Microservices with Spring Boot and Spring Cloud

☞ Spring Boot and OAuth2: Getting the Authorization Code

☞ An Introduction to Spring Boot

☞ How to build GraphQL APIs with Kotlin, Spring Boot, and MongoDB?

☞ Build a Rest API with Spring Boot using MySQL and JPA

☞ Angular 8 + Spring Boot 2.2: Build a CRUD App Today!

☞ Spring Boot vs. Spring MVC vs. Spring: How Do They Compare?

☞ Top 4 Spring Annotations for Java Developer in 2019


Originally published on developer.okta.com

Build a reactive Microservices Architecture using Spring Cloud Gateway, Spring Boot and Spring WebFlux

Build a reactive Microservices Architecture using Spring Cloud Gateway, Spring Boot and Spring WebFlux

In this article, you'll learn how you can build a reactive microservices architecture using Spring Cloud Gateway, Spring Boot, and Spring WebFlux.

Originally published by Matt Raible at https://developer.okta.com

So you wanna go full reactive, eh? Great! Reactive programming is an increasingly popular way to make your applications more efficient. Instead of making a call to a resource and waiting on a response, reactive applications asynchronously receive a response. This allows them to free up processing power, only perform processing when necessary, and scale more effectively than other systems.

The Java ecosystem has its fair share of reactive frameworks, including Play Framework, Ratpack, Vert.x, and Spring WebFlux. Like Reactive programming, a microservices architecture can help large teams scale quickly and is possible to build using any of the awesome aforementioned frameworks.

Today I’d like to show you how you can build a reactive microservices architecture using Spring Cloud Gateway, Spring Boot, and Spring WebFlux. We’ll leverage Spring Cloud Gateway as API gateways are often important components in a cloud-native microservices architecture, providing the aggregation layer for all your backend microservices.

This tutorial shows you how to build a microservice with a REST API that returns a list of new cars. You’ll use Eureka for service discovery and Spring Cloud Gateway to route requests to the microservice. Then you’ll integrate Spring Security so only authenticated users can access your API gateway and microservice.

Prerequisites: HTTPie (or cURL), Java 11+, and an internet connection.

Spring Cloud Gateway vs. Zuul

Zuul is Netflix’s API gateway. First released in 2013, Zuul was not originally reactive, but Zuul 2 is a ground-up rewrite to make it reactive. Unfortunately, Spring Cloud does not support Zuul 2 and it likely never will.

Spring Cloud Gateway is now the preferred API gateway implementation from the Spring Cloud Team. It’s built on Spring 5, Reactor, and Spring WebFlux. Not only that, it also includes circuit breaker integration, service discovery with Eureka, and is much easier to integrate with OAuth 2.0!

Let’s dig in.

Create a Spring Cloud Eureka Server Project

Start by creating a directory to hold all your projects, for example, spring-cloud-gateway. Navigate to it in a terminal window and create a discovery-service project that includes Spring Cloud Eureka Server as a dependency.

http https://start.spring.io/starter.zip javaVersion==11 artifactId==discovery-service \  name==eureka-service baseDir==discovery-service \  dependencies==cloud-eureka-server | tar -xzvf -
The command above uses HTTPie. I highly recommend installing it. You can also use curl. Run curl https://start.spring.io to see the syntax.

Add @EnableEurekaServer on its main class to enable it as a Eureka server.

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer

@SpringBootApplication

public class EurekaServiceApplication {...}

Add the following properties to the project’s src/main/resources/application.properties file to configure its port and turn off Eureka registration.

server.port=8761
eureka.client.register-with-eureka=false

To make the discovery-service run on Java 11+, add a dependency on JAXB.

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
</dependency>

Start the project using ./mvnw spring-boot:run or by running it in your IDE.

Create a Spring Cloud Gateway Project

Next, create an api-gateway project that includes a handful of Spring Cloud dependencies.

http https://start.spring.io/starter.zip javaVersion==11 artifactId==api-gateway 
 name==api-gateway baseDir==api-gateway
 dependencies==actuator,cloud-eureka,cloud-feign,cloud-gateway,cloud-hystrix,webflux,lombok | tar -xzvf -

We’ll come back to configuring this project in a minute.

Create a Reactive Microservice with Spring WebFlux

The car microservice will contain a significant portion of this example’s code because it contains a fully-functional REST API that supports CRUD (Create, Read, Update, and Delete).

Create the car-service project using start.spring.io:

http https://start.spring.io/starter.zip javaVersion==11 artifactId==car-service 
 name==car-service baseDir==car-service
 dependencies==actuator,cloud-eureka,webflux,data-mongodb-reactive,flapdoodle-mongo,lombok | tar -xzvf -

The dependencies argument is interesting in this command. You can see that Spring WebFlux is included, as is MongoDB. Spring Data provides reactive drivers for Redis and Cassandra as well.

You may also be interested in R2DBC (Reactive Relational Database Connectivity) - an endeavor to bring a reactive programming API to SQL databases. I did not use it in this example because it’s not yet available on start.spring.io.

Build a REST API with Spring WebFlux

I’m a big fan of VWs, especially classic ones like the bus and the bug. Did you know that VW has a bunch of electric vehicles coming out in the next few years? I’m really excited by the ID Buzz! It has classic curves and is all-electric. It even has 350+ horsepower!

In case you’re not familiar with the ID Buzz, here’s a photo from Volkswagen.

Let’s have some fun with this API example and use the electric VWs for our data set. This API will track the various car names and release dates.

Add Eureka registration, sample data initialization, and a reactive REST API to src/main/java/…​/CarServiceApplication.java:

package com.example.carservice;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Set;
import java.util.UUID;

@EnableEurekaClient
@SpringBootApplication
@Slf4j
public class CarServiceApplication {

   public static void main(String[] args) {
       SpringApplication.run(CarServiceApplication.class, args);
   }

   @Bean
   ApplicationRunner init(CarRepository repository) {
       // Electric VWs from
https://www.vw.com/electric-concepts/
       // Release dates from
https://www.motor1.com/features/346407/volkswagen-id-price-on-sale/
       Car ID = new Car(UUID.randomUUID(), "ID.", LocalDate.of(2019, Month.DECEMBER, 1));
       Car ID_CROZZ = new Car(UUID.randomUUID(), "ID. CROZZ", LocalDate.of(2021, Month.MAY, 1));
       Car ID_VIZZION = new Car(UUID.randomUUID(), "ID. VIZZION", LocalDate.of(2021, Month.DECEMBER, 1));
       Car ID_BUZZ = new Car(UUID.randomUUID(), "ID. BUZZ", LocalDate.of(2021, Month.DECEMBER, 1));
       Set<Car> vwConcepts = Set.of(ID, ID_BUZZ, ID_CROZZ, ID_VIZZION);

       return args -> {
           repository
                   .deleteAll()
                   .thenMany(
                           Flux
                                   .just(vwConcepts)
                                   .flatMap(repository::saveAll)
                   )
                   .thenMany(repository.findAll())
                   .subscribe(car -> log.info("saving " + car.toString()));
       };
   }
}

@Document
@Data
@NoArgsConstructor
@AllArgsConstructor
class Car {
   @Id
   private UUID id;
   private String name;
   private LocalDate releaseDate;
}

interface CarRepository extends ReactiveMongoRepository<Car, UUID> {
}

@RestController
class CarController {

   private CarRepository carRepository;

   public CarController(CarRepository carRepository) {
       this.carRepository = carRepository;
   }

   @PostMapping("/cars")
   @ResponseStatus(HttpStatus.CREATED)
   public Mono<Car> addCar(@RequestBody Car car) {
       return carRepository.save(car);
   }

   @GetMapping("/cars")
   public Flux<Car> getCars() {
       return carRepository.findAll();
   }

   @DeleteMapping("/cars/{id}")
   public Mono<ResponseEntity<Void>> deleteCar(@PathVariable("id") UUID id) {
       return carRepository.findById(id)
               .flatMap(car -> carRepository.delete(car)
                       .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
               )
               .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
   }
}

  1. Add the @EnableEurekaClient annotation for service discovery
  2. @Slf4j is a handy annotation from Lombok to enable logging in a class
  3. ApplicationRunner bean to populate MongoDB with default data
  4. Delete all existing data in MongoDB so new data is not additive
  5. Subscribe to results so both deleteAll() and saveAll() are invoked
  6. Car class with Spring Data NoSQL and Lombok annotations to reduce boilerplate
  7. CarRepository interface that extends ReactiveMongoRepository, giving you CRUDability with hardly any code!
  8. CarController class that uses CarRepository to perform CRUD actions
  9. Spring WebFlux returns a Mono publisher for single objects
  10. Return a Flex publisher for multiple objects

You’ll also need to modify the car-service project’s application.properties to set its name and port.

spring.application.name=car-service
server.port=8081

Run MongoDB

The easiest way to run MongoDB is to remove the test scope from the flapdoodle dependency in car-service/pom.xml. This will cause your app to start an embedded MongoDB dependency.

<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <!--<scope>test</scope>-->
</dependency>

You can also install and run MongoDB using Homebrew.

brew tap mongodb/brew
brew install [email protected]
mongod

Or, use Docker:

docker run -d -it -p 27017:27017 mongo

Stream Data with WebFlux

This completes everything you need to do to build a REST API with Spring WebFlux.

"But wait!" you might say. "I thought WebFlux was all about streaming data?"

In this particular example, you can still stream data from the /cars endpoint, but not in a browser.

A browser has no way to consume a stream other than using Server-Sent Events or WebSockets. Non-browser clients however can get a JSON stream by sending an Accept header with a value of application/stream+json .

You could test everything works at this point by firing up your browser and using HTTPie to make requests. However, it’s much better to write automated tests!

Test Your WebFlux API with WebTestClient

WebClient ships as part of Spring WebFlux and can be useful for making reactive requests, receiving responses, and populating objects with the payload. A companion class, WebTestClient, can be used to test your WebFlux API. It contains request methods that are similar to WebClient, as well as methods to check the response body, status, and headers.

Modify the src/test/java/…​/CarServiceApplicationTests.java class in the car-service project to contain the code below.

package com.example.carservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Collections;
import java.util.UUID;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class CarServiceApplicationTests {

    @Autowired
    CarRepository carRepository;

    @Autowired
    WebTestClient webTestClient;

    @Test
    public void testAddCar() {
        Car buggy = new Car(UUID.randomUUID(), "ID. BUGGY", LocalDate.of(2022, Month.DECEMBER, 1));

        webTestClient.post().uri("/cars")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .body(Mono.just(buggy), Car.class)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBody()
                .jsonPath("$.id").isNotEmpty()
                .jsonPath("$.name").isEqualTo("ID. BUGGY");
    }

    @Test
    public void testGetAllCars() {
        webTestClient.get().uri("/cars")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBodyList(Car.class);
    }

    @Test
    public void testDeleteCar() {
        Car buzzCargo = carRepository.save(new Car(UUID.randomUUID(), "ID. BUZZ CARGO",
                LocalDate.of(2022, Month.DECEMBER, 2))).block();

        webTestClient.delete()
                .uri("/cars/{id}", Collections.singletonMap("id", buzzCargo.getId()))
                .exchange()
                .expectStatus().isOk();
    }
}

To prove it works, run ./mvnw test. Give yourself a pat on the back when your tests pass!

If you’re on Windows, use mvnw test.

Use Spring Cloud Gateway with Reactive Microservices

To edit all three projects in the same IDE window, I find it useful to create an aggregator pom.xml. Create a pom.xml file in the parent directory of your projects and copy the XML below into it.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.okta.developer</groupId>
    <artifactId>reactive-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>reactive-parent</name>
    <modules>
        <module>discovery-service</module>
        <module>car-service</module>
        <module>api-gateway</module>
    </modules>
</project>

After creating this file, you should be able to open it in your IDE as a project and navigate between projects easily.

In the api-gateway project, add @EnableEurekaClient to the main class to make it Eureka-aware.

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class ApiGatewayApplication {...}

Then, modify the src/main/resources/application.properties file to configure the application name.

spring.application.name=gateway

Create a RouteLocator bean in ApiGatewayApplication to configure routes. You can configure Spring Cloud Gateway with YAML, but I prefer Java.

package com.example.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

@EnableEurekaClient
@SpringBootApplication
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("car-service", r -> r.path("/cars")
                        .uri("lb://car-service"))
                .build();
    }
}

After making these code changes, you should be able to start all three Spring Boot apps and hit http://localhost:8080/cars.

$ http :8080/cars
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
transfer-encoding: chunked

[
    {
        "id": "ff48f617-6cba-477c-8e8f-2fc95be96416",
        "name": "ID. CROZZ",
        "releaseDate": "2021-05-01"
    },
    {
        "id": "dd6c3c32-724c-4511-a02c-3348b226160a",
        "name": "ID. BUZZ",
        "releaseDate": "2021-12-01"
    },
    {
        "id": "97cfc577-d66e-4a3c-bc40-e78c3aab7261",
        "name": "ID.",
        "releaseDate": "2019-12-01"
    },
    {
        "id": "477632c8-2206-4f72-b1a8-e982e6128ab4",
        "name": "ID. VIZZION",
        "releaseDate": "2021-12-01"
    }
]

Add a REST API to Retrieve Your Favorite Cars

Create a /fave-cars endpoint that strips out cars that aren’t your favorite.

First, add a load-balanced WebClient.Builder bean.

@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder();
}

Then add a Car POJO and a FaveCarsController below the ApiGatewayApplication class in the same file.

public class ApiGatewayApplication {...}
class Car {...}
class FaveCarsController {...}

Use WebClient to retrieve the cars and filter out the ones you don’t love.

@Data
class Car {
    private String name;
    private LocalDate releaseDate;
}

@RestController
class FaveCarsController {

    private final WebClient.Builder carClient;

    public FaveCarsController(WebClient.Builder carClient) {
        this.carClient = carClient;
    }

    @GetMapping("/fave-cars")
    public Flux<Car> faveCars() {
        return carClient.build().get().uri("lb://car-service/cars")
                .retrieve().bodyToFlux(Car.class)
                .filter(this::isFavorite);
    }

    private boolean isFavorite(Car car) {
        return car.getName().equals("ID. BUZZ");
    }
}

If you’re not using an IDE that auto-imports for you, you’ll want to copy/paste the following into the top of ApiGatewayApplication.java:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

Restart your gateway app to see the http://localhost:8080/fave-cars endpoint only returns the ID Buzz.

What about Failover with Hystrix?

Spring Cloud Gateway only supports Hystrix at the time of this writing. Spring Cloud deprecated direct support for Hystrix in favor of Spring Cloud Circuit Breaker. Unfortunately, this library hasn’t had a GA release yet, so I decided not to use it.

To use Hystrix with Spring Cloud Gateway, you can add a filter to your car-service route, like so:

.route("car-service", r -> r.path("/cars")
        .filters(f -> f.hystrix(c -> c.setName("carsFallback")
                .setFallbackUri("forward:/cars-fallback")))
        .uri("lb://car-service/cars"))
.build();

Then create a CarsFallback controller to handle the /cars-fallback route.

@RestController
class CarsFallback {

    @GetMapping("/cars-fallback")
    public Flux<Car> noCars() {
        return Flux.empty();
    }
}

First, restart your gateway and confirm http://localhost:8080/cars works. Then shut down the car service, try again, and you’ll see it now returns an empty array. Restart the car service and you’ll see the list populated again.

You’ve built a resilient and reactive microservices architecture with Spring Cloud Gateway and Spring WebFlux. Now let’s see how to secure it!

What about Feign with Spring Cloud Gateway?

If you’d like to use Feign in a WebFlux app, see the feign-reactive project. I did not have a need for Feign in this particular example.

Secure Spring Cloud Gateway with OAuth 2.0

OAuth 2.0 is an authorization framework for delegated access to APIs. OIDC (or OpenID Connect) is a thin layer on top of OAuth 2.0 that provides authentication. Spring Security has excellent support for both frameworks and so does Okta!

You can use OAuth 2.0 and OIDC without a cloud identity provider by building your own server or by using an open-source implementation. However, wouldn’t you rather just use something that’s always on, like Okta?

If you already have an Okta account, see the Create a Web Application in Okta sidebar below. Otherwise, we created a Maven plugin that configures a free Okta developer account + an OIDC app (in under a minute!).

To use it, add the following plugin repository to your gateway project’s pom.xml:

<pluginRepositories>
    <pluginRepository>
        <id>ossrh</id>
        <releases><enabled>false</enabled></releases>
        <snapshots><enabled>true</enabled></snapshots>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </pluginRepository>
</pluginRepositories>

Then run ./mvnw com.okta:okta-maven-plugin:setup to create an account and configure your Spring Boot app to work with Okta.

Create a Web Application in Okta

Log in to your Okta Developer account (or sign up if you don’t have an account).

  1. From the Applications page, choose Add Application.
  2. On the Create New Application page, select Web.
  3. Give your app a memorable name, add http://localhost:8080/login/oauth2/code/okta as a Login redirect URI, select Refresh Token (in addition to Authorization Code), and click Done.

Copy the issuer (found under API > Authorization Servers), client ID, and client secret into application.properties for both projects.

okta.oauth2.issuer=$issuer
okta.oauth2.client-id=$clientId
okta.oauth2.client-secret=$clientSecret

Next, add the Okta Spring Boot starter and Spring Cloud Security to your gateway’s pom.xml:

<dependency>
   <groupId>com.okta.spring</groupId>
   <artifactId>okta-spring-boot-starter</artifactId>
   <version>1.2.1</version>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-security</artifactId>
</dependency>

This is all you need to do to add OIDC login with Okta! Restart your Gateway app and navigate to http://localhost:8080/fave-cars in your browser to be redirected to Okta for user authorization.

Make Your Gateway an OAuth 2.0 Resource Server

You likely won’t build the UI for your app on the gateway itself. You’ll probably use a SPA or mobile app instead. To configure your gateway to operate as a resource server (that looks for an Authorization header with a bearer token), add a new SecurityConfiguration class in the same directory as your main class.

package com.example.apigateway;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .oauth2Login()
                .and()
            .oauth2ResourceServer()
                .jwt();
        return http.build();
        // @formatter:on
    }
}

CORS with Spring Cloud Gateway

If you’re using a SPA for your UI, you’ll want to configure CORS as well. You can do this by adding a CorsWebFilter bean to this class.

@Bean
CorsWebFilter corsWebFilter() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(List.of(""));
    corsConfig.setMaxAge(3600L);
    corsConfig.addAllowedMethod("
");
    corsConfig.addAllowedHeader("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);
}

Make sure your imports match the ones below.

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

Spring Cloud Gateway’s documentation explains how to configure CORS with YAML or with WebFluxConfigurer. Unfortunately, I was unable to get either one to work.

Test Your Gateway with WebTestClient and JWT

If you configured CORS in your gateway, you can test it works with WebTestClient. Replace the code in ApiGatewayApplicationTests with the following.

package com.example.apigateway;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class ApiGatewayApplicationTests {

    @Autowired
    WebTestClient webTestClient;

    @MockBean
    ReactiveJwtDecoder jwtDecoder;

    @Test
    public void testCorsConfiguration() {
        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
        WebTestClient.ResponseSpec response = webTestClient.put().uri("/")
                .headers(addJwt(jwt))
                .header("Origin", "http://example.com")
                .exchange();

        response.expectHeader().valueEquals("Access-Control-Allow-Origin", "*");
    }

    private Jwt jwt() {
        return new Jwt("token", null, null,
                Map.of("alg", "none"), Map.of("sub", "betsy"));
    }

    private Consumer<HttpHeaders> addJwt(Jwt jwt) {
        return headers -> headers.setBearerAuth(jwt.getTokenValue());
    }
}

  1. Mock ReactiveJwtDecoder so you can set expectations and return mocks when it decodes
  2. Create a new JWT
  3. Return the same JWT when it’s decoded
  4. Add the JWT to the Authorization header with a Bearer prefix

I like how WebTestClient allows you to set the security headers so easily!

You’ve configured Spring Cloud Gateway to use OIDC login and function as an OAuth 2.0 resource server, but the car service is still available on port 8081. Let’s fix that so only the gateway can talk to it.

Secure Gateway to Microservice Communication

Add the Okta Spring Boot starter to car-service/pom.xml:

<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-boot-starter</artifactId>
    <version>1.2.1</version>
</dependency>

Copy the okta.* properties from the gateway’s application.properties to the car service’s. Then create a SecurityConfiguration class to make the app an OAuth 2.0 resource server.

package com.example.carservice;

import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .oauth2ResourceServer()
                .jwt();

        Okta.configureResourceServer401ResponseBody(http);

        return http.build();
        // @formatter:on
    }
}

That’s it! Restart your car service application and it’s now protected from anonymous intruders.

$ http :8081/cars
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: text/plain
...

401 Unauthorized

Test Your Microservice with WebTestClient and JWT

The tests you added in the car-service project will no longer work now that you’ve enabled security. Modify the code in CarServiceApplicationTests.java to add JWT access tokens to each request.

package com.example.carservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.Month;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"spring.cloud.discovery.enabled = false"})
public class CarServiceApplicationTests {

    @Autowired
    CarRepository carRepository;

    @Autowired
    WebTestClient webTestClient;

    @MockBean
    ReactiveJwtDecoder jwtDecoder;

    @Test
    public void testAddCar() {
        Car buggy = new Car(UUID.randomUUID(), "ID. BUGGY", LocalDate.of(2022, Month.DECEMBER, 1));

        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.post().uri("/cars")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .headers(addJwt(jwt))
                .body(Mono.just(buggy), Car.class)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBody()
                .jsonPath("$.id").isNotEmpty()
                .jsonPath("$.name").isEqualTo("ID. BUGGY");
    }

    @Test
    public void testGetAllCars() {
        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.get().uri("/cars")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .headers(addJwt(jwt))
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBodyList(Car.class);
    }

    @Test
    public void testDeleteCar() {
        Car buzzCargo = carRepository.save(new Car(UUID.randomUUID(), "ID. BUZZ CARGO",
                LocalDate.of(2022, Month.DECEMBER, 2))).block();

        Jwt jwt = jwt();
        when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));

        webTestClient.delete()
               .uri("/cars/{id}", Map.of("id", buzzCargo.getId()))
                .headers(addJwt(jwt))
                .exchange()
                .expectStatus().isOk();
    }

    private Jwt jwt() {
        return new Jwt("token", null, null,
                Map.of("alg", "none"), Map.of("sub", "dave"));
    }

    private Consumer<HttpHeaders> addJwt(Jwt jwt) {
        return headers -> headers.setBearerAuth(jwt.getTokenValue());
    }
}

Run the test again and everything should pass!

Mock JWT Support in Spring Security 5.2

Kudos to Josh Cummings for his help with JWTs and WebTestClient. Josh gave me a preview of the mock JWT support coming in Spring Security 5.2.

this.webTestClient.mutateWith(jwt()).post(...)

Josh also provided an example test showing how to mock a JWT’s subject, scope, and claims. This code is based on new functionality in Spring Security 5.2.0.M3.

The future is bright for OAuth 2.0 and JWT support in Spring Security land! 😎

Relay the Access Token: Gateway to Microservice

You only need to make one small change for your gateway to talk to this protected service. It’s incredibly easy and I ❤️ it!

In ApiGatewayApplication.java, add a filter that applies the TokenRelayGatewayFilterFactory from Spring Cloud Security.

import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
                                       TokenRelayGatewayFilterFactory filterFactory) {
    return builder.routes()
            .route("car-service", r -> r.path("/cars")
                    .filters(f -> f.filter(filterFactory.apply()))
                    .uri("lb://car-service/cars"))
            .build();
}

This relay factory does not automatically refresh access tokens (yet).

Restart your API gateway and you should be able to view http://localhost:8080/cars and have everything work as expected.

Pretty sweet, don’t you think?!

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about Microservices

An Introduction to Microservices

What is Microservices?

Build Spring Microservices and Dockerize Them for Production

Best Java Microservices Interview Questions In 2019

Build a microservices architecture with Spring Boot and Spring Cloud

Design patterns for microservices 🍂 🍂 🍂

Kotlin Microservices With Micronaut, Spring Cloud, and JPA

Build Spring Microservices and Dockerize Them for Production

Secure Service-to-Service Spring Microservices with HTTPS and OAuth 2.0

Build Secure Microservices with AWS Lambda and ASP.NET Core


Learn Spring Framework and Spring Boot - Build a Spring Web Application

Learn Spring Framework and Spring Boot - Build a Spring Web Application: If you're new to the Spring Framework, this is the course you want to start with. This course covers the core of the Spring Framework, the foundation which all of the other Spring Framework projects are built from.

Build a web application using Spring Framework and *Spring Boot *

If you're new to the Spring Framework, this is the course you want to start with. This course covers the core of the Spring Framework, the foundation which all of the other Spring Framework projects are built from.

In this course, you will learn about important key concepts, such as dependency injection and inversion of control, which are used throughout the Spring Framework. Within the Spring Framework, you have the option of using the traditional XML configuration, or the new Java based configuration. I'll show you step by step how to configure Spring Beans using best practices in XML and Java. I'll also show you how to use Spring to persist data into a database, and Spring MVC to show content from the database on a webpage.

By the time we reach the end of this course, you will be able to build a functioning Spring Web Application.

In this course, you will learn about:

  • Dependency Injection and Inversion of Control (IoC) in the Spring Framework.
  • Spring Boot
  • Using Spring Initializr
  • Using Maven to build Spring Projects
  • How to use JUnit and Mockito to test Spring
  • Java and XML Spring Configuration
  • Spring MVC and Thymeleaf with Bootstrap CSS
  • Spring MVC Test
  • JPA / Hibernate
  • Spring JPA and using DAOs
  • Spring Profiles