Microservices Spring Boot Tutorial | Spring Frameworks | Simpliv

Microservices Spring Boot Tutorial | Spring Frameworks | Simpliv

Want to be able to generate a microservice infrastructure that organizes your application using different layers following the MVC pattern? Then, this course is for you! Simpliv brings you global expertise to allow you to build a microservice infrastructure to realize a full stack web application.

Description
This course is a practical course which explains how to realise a microservice infrastructure in order to realise a full-stack web application composed by a "framework-less" Single Page Application and two restful Spring Boot microservices interacting with each other and with two different databases thanks to the implementation of the JPA (Java Persistence Api).

You'll have the basis to generate fastly a microservice infrastructure, to organise your application using different layers according to the "SOLID" principle and following the MVC pattern.

You will have the instrument to organise your communication protocols using simple and effective rules, to implement restful web services which communicate with Json Messages.

This is course has the same topic of the Italian course Sviluppo Web a Microservizi con Java Spring Boot e Ajax" so it can easily considered as its English version. The example developed is different but with the same topics: microservices, Spring Boot, JPA and Rest calls.

Who is the target audience?

Anybody who wants to understand microservice infrastructure and the difference existing between monolithic application and microservices
Anybody who wants to learn Spring Boot and how to realize fastly a microservice infrastructure using this precious instrument
Anybody who wants to understand the difference between the classical use of Spring MVC and its implementation through Spring Boot
Anyone who wants to learn the basis of JPA (Java Persistence Api) in order to interact with Databases
Anyone who wants to learn how to implement Restful Web services using a microservice infrastructure
Anyone who wants to learn how to consume Restful web service either from the user interface than from server-side code
Anyone who want to have a basic idea on Single Page Applications and how they works
Anyone who wants to realise a full stack web application using the most modern technologies and approaches
Anyone who wants to learn how Ajax calls and javascript can be used to let our web pages become dynamics
Anybody who wants to understand how to generate a dynamic web page using multiple resources con temporarily
Basic knowledge
A few basis of Object Oriented Language in order to understand better the practical implementation of the code
What will you learn
Create two Spring boot restful web services implementing the JPA for the interaction with the databases
Create a full stack web application composed of a microservices infrastructure and a single page application
Learn how two microservices can communicate with each-other and exchange information through JSON messages
This is course has the same topic of the Italian course "Sviluppo Web a Microservizi con Java Spring Boot e Ajax" so it can easily considered as its English version. The example developed is different but with the same topics: microservices, Spring Boot, JPA and Rest calls

To continue:

Spring Boot Best Practices for Microservices

Spring Boot Best Practices for Microservices

In this Microservice configuration with Spring Boot tutorial, I cover some best practices and common patterns for using Spring Boot configuration for microservices including what config mechanism to use where and how to save sensitive config like passwords and connection strings. Microservices configuration Best Practices - Microservice configuration with Spring Boot

In this video, I cover some best practices and common patterns for using Spring Boot configuration for microservices including what config mechanism to use where and how to save sensitive config like passwords and connection strings.

Kotlin Microservices With Micronaut, Spring Cloud, and JPA

Kotlin Microservices With Micronaut, Spring Cloud, and JPA

In this article, we'll learn how to create microservice applications written in Kotlin, using the Micronaut framework, Spring Cloud, and JPA for a little extra help.

In this article, we'll learn how to create microservice applications written in Kotlin, using the Micronaut framework, Spring Cloud, and JPA for a little extra help.

The Micronaut Framework provides support for Kotlin built upon the Kapt compiler plugin. It also implements the most popular cloud-native patterns, like distributed configuration, service discovery, and client-side load balancing. These features allow you to include an application that’s been built on top of Micronaut into an existing microservices-based system. The most popular example of such an approach may be integration with the Spring Cloud ecosystem. If you have already used Spring Cloud, it is very likely you built your microservices-based architecture using the Eureka discovery server and Spring Cloud Config as a configuration server. Beginning with version 1.1, Micronaut supports both these popular tools as part of the Spring Cloud project. That’s good news because, in version 1.0, the only supported distributed solution was Consul, and there was no way to use Eureka discovery together with Consul’s property source (running them together ends with an exception).

In this article, you will learn how to:

  • configure Micronaut Maven support for Kotlin using the Kapt compiler.
  • implement microservices with Micronaut and Kotlin.
  • integrate Micronaut with the Spring Cloud Eureka discovery server.
  • integrate Micronaut with the Spring Cloud Config server.
  • configure JPA/Hibernate support for an application built on top Micronaut.
  • run a single instance of PostgreSQL shared between all sample microservices.

We also have three microservices that communicate with each other. We use Spring Cloud Eureka and Spring Cloud Config for discovery and distributed configuration instead of Consul. Every service has a back-end store — PostgreSQL database. This architecture has been visualized in the following picture.

After that short introduction, we may proceed to the development. Let’s begin from configuring Kotlin support for Micronaut.

1. Kotlin With Micronaut — Configuration

Support for Kotlin with the Kapt compiler plugin is described well on the Micronaut docs site. However, I decided to use Maven instead of Gradle, so our configuration will be slightly different than instructions for Gradle. We configure Kapt inside the Maven plugin for Kotlin, kotlin-maven-plugin. Thanks to that Kapt, we will create Java “stub” classes for each of our Kotlin classes, which can then be processed by Micronaut’s Java annotation processor. The Micronaut annotation processors are declared inside the tag annotationProcessorPaths, in the configuration section. Here’s the full Maven configuration to provide support for Kotlin. Beside the core library micronaut-inject-java, we also use annotations from tracing, OpenAPI, and JPA libraries.

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
    <configuration>
        <jvmTarget>1.8</jvmTarget>
    </configuration>
    <executions>
        <execution>
            <id>compile</id>
            <phase>compile</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <phase>test-compile</phase>
            <goals>
                <goal>test-compile</goal>
            </goals>
        </execution>
        <execution>
            <id>kapt</id>
            <goals>
                <goal>kapt</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>src/main/kotlin</sourceDir>
                </sourceDirs>
                <annotationProcessorPaths>
                    <annotationProcessorPath>
                        <groupId>io.micronaut</groupId>
                        <artifactId>micronaut-inject-java</artifactId>
                        <version>${micronaut.version}</version>
                    </annotationProcessorPath>
                    <annotationProcessorPath>
                        <groupId>io.micronaut.configuration</groupId>
                        <artifactId>micronaut-openapi</artifactId>
                        <version>${micronaut.version}</version>
                    </annotationProcessorPath>
                    <annotationProcessorPath>
                        <groupId>io.micronaut</groupId>
                        <artifactId>micronaut-tracing</artifactId>
                        <version>${micronaut.version}</version>
                    </annotationProcessorPath>
                    <annotationProcessorPath>
                        <groupId>javax.persistence</groupId>
                        <artifactId>javax.persistence-api</artifactId>
                        <version>2.2</version>
                    </annotationProcessorPath>
                    <annotationProcessorPath>
                        <groupId>io.micronaut.configuration</groupId>
                        <artifactId>micronaut-hibernate-jpa</artifactId>
                        <version>1.1.0.RC2</version>
                    </annotationProcessorPath>
                </annotationProcessorPaths>
            </configuration>
        </execution>
    </executions>
</plugin>

We also should not run maven-compiler-plugin during the compilation phase. The Kapt compiler generates Java classes, so we don’t need to run any other compilators during the build.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <proc>none</proc>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
    <executions>
        <execution>
            <id>default-compile</id>
            <phase>none</phase>
        </execution>
        <execution>
            <id>default-testCompile</id>
            <phase>none</phase>
        </execution>
        <execution>
            <id>java-compile</id>
            <phase>compile</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>java-test-compile</id>
            <phase>test-compile</phase>
            <goals>
                <goal>testCompile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Finally, we will add the Kotlin core library and Jackson module for JSON serialization.

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>${kotlin.version}</version>
</dependency>

If you are running the application with IntelliJ, you should first enable annotation processing. To do that, go to Build, Execution, Deployment -> Compiler -> Annotation Processors as shown below.

2. Running Postgres

Before proceeding to the development phase, we have to start an instance of the PostgreSQL database. It will be started as a Docker container. For me, PostgreSQL is now available under the address,192.168.99.100:5432, because I’m using Docker Toolbox.

$ docker run -d --name postgres -e POSTGRES_USER=micronaut -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=micronaut -p 5432:5432 postgres

3. Enabling Hibernate for Micronaut

Hibernate configuration is a little harder for Micronaut than for Spring Boot. We don’t have any projects like Spring Data JPA, where almost everything is auto-configured. Beside specific JDBC drivers for integration with a database, we have to include the following dependencies. We may choose between three available libraries that provide data source implementation: Tomcat, Hikari, or DBCP.

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>
<dependency>
    <groupId>io.micronaut.configuration</groupId>
    <artifactId>micronaut-jdbc-hikari</artifactId>
</dependency>
<dependency>
    <groupId>io.micronaut.configuration</groupId>
    <artifactId>micronaut-hibernate-jpa</artifactId>
</dependency>
<dependency>
    <groupId>io.micronaut.configuration</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

The next step is to provide some configuration settings. All the properties will be stored on the configuration server. We have to set the database connection settings and credentials. The JPA configuration settings are provided under the jpa.* key. We force Hibernate to update the database on application startup and print all the SQL logs (only for tests).

datasources:
  default:
    url: jdbc:postgresql://192.168.99.100:5432/micronaut?ssl=false
    username: micronaut
    password: 123456
    driverClassName: org.postgresql.Driver
jpa:
  default:
    packages-to-scan:
      - 'pl.piomin.services.department.model'
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        show_sql: true

Here’s our sample domain object.

@Entity
data class Department(@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "department_id_seq") @SequenceGenerator(name = "department_id_seq", sequenceName = "department_id_seq") var id: Long,
                      var organizationId: Long, var name: String) {

    @Transient
    var employees: MutableList<Employee> = mutableListOf()

}

The repository bean needs to inject EntityManager using the @PersistentContext and @CurrentSession annotations. All functions need to be annotated with @Transactional, which does not require the methods to be final (open modifier in Kotlin).

@Singleton
open class DepartmentRepository(@param:CurrentSession @field:PersistenceContext val entityManager: EntityManager) {

    @Transactional
    open fun add(department: Department): Department {
        entityManager.persist(department)
        return department
    }

    @Transactional(readOnly = true)
    open fun findById(id: Long): Department = entityManager.find(Department::class.java, id)

    @Transactional(readOnly = true)
    open fun findAll(): List<Department> = entityManager.createQuery("SELECT d FROM Department d").resultList as List<Department>

    @Transactional(readOnly = true)
    open fun findByOrganization(organizationId: Long) = entityManager.createQuery("SELECT d FROM Department d WHERE d.organizationId = :orgId")
            .setParameter("orgId", organizationId)
            .resultList as List<Department>

}

4. Running Spring Cloud Config Server

Running the Spring Cloud Config server is very simple. I have already described that in some of my previous articles. All those were prepared for Java, while today we start it as Kotlin application. Here’s our main class. It should be annotated with @EnableConfigServer.

@SpringBootApplication
@EnableConfigServer
class ConfigApplication

fun main(args: Array<String>) {
    runApplication<ConfigApplication>(*args)
}

Besides the Kotlin core dependency, we need to include the artifact, spring-cloud-config-server.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>${kotlin.version}</version>
</dependency>

By default, the config server tries to use Git as a properties source backend. We prefer using classpath resources, which is much simpler for our tests. To do that, we have to enable the native profile. We will also set the server port to 8888.

spring:
  application:
    name: config-service
  profiles:
    active: native
server:
  port: 8888

If you place all the configurations under the directory /src/main/resources/config, they will be loaded automatically after the app starts up.

Here’s the configuration file for department-service.

micronaut:
  server:
    port: -1
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
datasources:
  default:
    url: jdbc:postgresql://192.168.99.100:5432/micronaut?ssl=false
    username: micronaut
    password: 123456
    driverClassName: org.postgresql.Driver
jpa:
  default:
    packages-to-scan:
      - 'pl.piomin.services.department.model'
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        show_sql: true
endpoints:
  info:
    enabled: true
    sensitive: false
eureka:
  client:
    registration:
      enabled: true
    defaultZone: "localhost:8761"

5. Running a Eureka Server

The Eureka server will also be run as a Spring Boot application written in Kotlin.

@SpringBootApplication
@EnableEurekaServer
class DiscoveryApplication

fun main(args: Array<String>) {
    runApplication<DiscoveryApplication>(*args)
}

We also need to include a single dependency, spring-cloud-starter-netflix-eureka-server, beside kotlin-stdlib-jdk8.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>${kotlin.version}</version>
</dependency>

We run a standalone instance of Eureka on port 8761.

spring:
  application:
    name: discovery-service
server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

6. Integrating Micronaut With Spring Cloud

The implementation of a distributed configuration client is automatically included in Micronaut’s core. We only need to include the module for service discovery.

<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-discovery-client</artifactId>
</dependency>

We don’t have to place anything in the source code. All the features can be enabled via configuration settings. First, we need to enable the config client by setting the micronaut.config-client.enabled property to true. The next step is to enable specific implementations of the config client — in our case, Spring Cloud Config — and then set the target URL.

micronaut:
  application:
    name: department-service
  config-client:
    enabled: true
spring:
  cloud:
    config:
      enabled: true
      uri: http://localhost:8888/

Each application fetches the properties frthe om configuration server. The part of the configuration responsible for enabling discovery based on the Eureka server is visible below.

eureka:
  client:
    registration:
      enabled: true
    defaultZone: "localhost:8761"

7. Running Applications

Kapt needs to be able to compile Kotlin code to Java succesfully. That’s why we place a method inside the class declaration, and annotate it with @JvmStatic. The main class visible below is also annotated with @OpenAPIDefinition in order to generate a Swagger definition for API methods.

@OpenAPIDefinition(
        info = Info(
                title = "Departments Management",
                version = "1.0",
                description = "Department API",
                contact = Contact(url = "https://piotrminkowski.wordpress.com", name = "Piotr Mińkowski", email = "[email protected]")
        )
)
open class DepartmentApplication {

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            Micronaut.run(DepartmentApplication::class.java)
        }
    }

}

Here’s the controller class from department-service. It injects a repository bean for database integration and EmployeeClient for HTTP communication with employee-service.

@Controller("/departments")
open class DepartmentController(private val logger: Logger = LoggerFactory.getLogger(DepartmentController::class.java)) {

    @Inject
    lateinit var repository: DepartmentRepository
    @Inject
    lateinit var employeeClient: EmployeeClient

    @Post
    fun add(@Body department: Department): Department {
        logger.info("Department add: {}", department)
        return repository.add(department)
    }

    @Get("/{id}")
    fun findById(id: Long): Department? {
        logger.info("Department find: id={}", id)
        return repository.findById(id)
    }

    @Get
    fun findAll(): List<Department> {
        logger.info("Department find")
        return repository.findAll()
    }

    @Get("/organization/{organizationId}")
    @ContinueSpan
    open fun findByOrganization(@SpanTag("organizationId") organizationId: Long): List<Department> {
        logger.info("Department find: organizationId={}", organizationId)
        return repository.findByOrganization(organizationId)
    }

    @Get("/organization/{organizationId}/with-employees")
    @ContinueSpan
    open fun findByOrganizationWithEmployees(@SpanTag("organizationId") organizationId: Long): List<Department> {
        logger.info("Department find: organizationId={}", organizationId)
        val departments = repository.findByOrganization(organizationId)
        departments.forEach { it.employees = employeeClient.findByDepartment(it.id) }
        return departments
    }

}

It is worth taking a look at the HTTP client implementation. It has been discussed in the details in my last article about Micronaut, A Quick Guide to Microservices with the Micronaut Framework.

@Client(id = "employee-service", path = "/employees")
interface EmployeeClient {

@Get("/department/{departmentId}")
fun findByDepartment(departmentId: Long): MutableList<Employee>

}

You can run all the microservices using IntelliJ. You may also build the whole project with Maven using themvn clean install command, and then run them using the java -jar command. Thanks to maven-shade-plugin, our applications will be generated as uber jars. We then can run them in the following order: config-service, discovery-service, and microservices.

$ java -jar config-service/target/config-service-1.0-SNAPSHOT.jar
$ java -jar discovery-service/target/discovery-service-1.0-SNAPSHOT.jar
$ java -jar employee-service/target/employee-service-1.0-SNAPSHOT.jar
$ java -jar department-service/target/department-service-1.0-SNAPSHOT.jar
$ java -jar organization-service/target/organization-service-1.0-SNAPSHOT.jar

After this, you may take a look at the Eureka dashboard available under the address http://localhost:8761 in order to see a list of the running services. You may also perform some tests by running HTTP API methods.

Summary

The sample application’s source code is available on GitHub in the repository sample-micronaut-microservices in the Kotlin branch. You can refer to that repository for more implementation details that have not been included in this article.

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