Adopting a microservices architecture provides unique opportunities to add failover and resiliency to your systems so your components can gracefully handle load spikes and errors. Microservices make change less expensive, too. They can also be a good idea when you have a large team working on a single product. You can break up your project into components that can function independently. Once components can operate independently, they can be built, tested, and deployed separately. This gives an organization and its teams the agility to develop and deploy quickly.
Java is an excellent language with a vast open source ecosystem for developing a microservice architecture. In fact, some of the biggest names in our industry use Java and contribute to its ecosystem. Have you ever heard of Netflix, Amazon, or Google? What about eBay, Twitter, and LinkedIn? Yes, web-scale companies handling incredible traffic are doing it with Java.
Implementing a microservices architecture in Java isn't for everyone. For that matter, implementing microservices, in general, isn't often needed. Most companies do it to scale their people, not their systems. Even Martin Fowler's original blog post on Microservices recommends against it:
One reasonable argument we've heard is that you shouldn't start with a microservices architecture. Instead begin with a monolith, keep it modular, and split it into microservices once the monolith becomes a problem.
The Java ecosystem has some well-established patterns for developing microservice architectures. If you're familiar with Spring, you'll feel right at home developing with Spring Boot and Spring Cloud. Since that's one of the quickest ways to get started, I figured I'd walk you through a quick example.
This example contains a microservice with a REST API that returns a list of cool cars. It uses Netflix Eureka for service discovery, WebClient for remote communication, and Spring Cloud Gateway to route requests to the microservice. It integrates Spring Security and OAuth 2.0, so only authenticated users can access the API gateway and microservice. It also uses Resilience4j to add fault tolerance to the gateway.
Here is a diagram showing the overall infrastructure:
Create Java Microservices with Spring Boot and Spring Cloud
I like to show developers how to build everything from scratch. Today, I'm going to take a different approach. First, I'll show you how to get the completed example working. Then, I'll explain how I created everything and the trials and tribulations I encountered along the way.
You can start by cloning the @oktadev/auth0-java-microservices-examples repository.
git clone https://github.com/oktadev/auth0-java-microservices-examples
There are two directories in this repository that pertain to this tutorial:
- spring-boot-gateway-webflux: a Spring Boot microservice architecture with Spring Cloud Gateway and Spring WebFlux.
- spring-boot-gateway-mvc: a Spring Boot microservice architecture with Spring Cloud Gateway and Spring MVC.
Each directory contains three projects:
- discovery-service: a Netflix Eureka server used for service discovery.
- car-service: a simple Car Service that uses Spring Data REST to serve up a REST API of cars.
- api-gateway: an API gateway with a
endpoint that talks to the car service and filters out cars that aren't cool (in my opinion, of course)./cool-cars
The configuration for the WebFlux and MVC implementations is the same, so choose one and follow along.
You can also watch a demo of the WebFlux example in the screencast below.
Run a Secure Spring Boot Microservice Architecture
To run the example, you must install the Auth0 CLI and create an Auth0 account. If you don't have an Auth0 account, sign up for free. I recommend using SDKMAN! to install Java 17+ and HTTPie for making HTTP requests.
First, start the discovery service:
cd discovery-service ./gradlew bootRun
Before you can start the API gateway project, you'll need to configure the API gateway to use your Auth0 account.
Open a terminal and run
auth0 login
to configure the Auth0 CLI to get an API key for your tenant. Then, run auth0 apps create
to register an OpenID Connect (OIDC) app with the appropriate URLs:auth0 apps create \ --name "Kick-Ass Cars" \ --description "Microservices for Cool Cars" \ --type regular \ --callbacks http://localhost:8080/login/oauth2/code/okta \ --logout-urls http://localhost:8080 \ --reveal-secrets
Copy
api-gateway/.env.example
to .env
and edit it to contain the values from the command above.OKTA_OAUTH2_ISSUER=https://<your-auth0-domain>/ OKTA_OAUTH2_CLIENT_ID= OKTA_OAUTH2_CLIENT_SECRET= OKTA_OAUTH2_AUDIENCE=https://<your-auth0-domain>/api/v2/
At startup, these properties will be read using spring-dotenv.
Run
./gradlew bootRun
to start the API gateway, or use your IDE to run it.Copy
car-service/.env.example
to .env
and update its values.OKTA_OAUTH2_ISSUER=https://<your-auth0-domain>/ OKTA_OAUTH2_AUDIENCE=https://<your-auth0-domain>/api/v2/
Start it with
./gradlew bootRun
and open http://localhost:8080
in your favorite browser. You'll be redirected to Auth0 to log in:After authenticating, you'll see your name in lights! ✨
You can navigate to the following URLs in your browser for different results:
: prints access token to the terminalhttp://localhost:8080/print-token
: returns a list of cool carshttp://localhost:8080/cool-cars
: proxies request to the car service and prints JWT claims in this application's terminalhttp://localhost:8080/home
You can see the access token's contents by copying/pasting it into jwt.io. You can also access the car service directly using it.
TOKEN=<access-token> http :8090/cars Authorization:"Bearer $TOKEN"
Pretty cool, eh? 😎
My Developer Story with Spring Boot and Spring Cloud
A few years ago, I created an example similar to this one with Spring Boot 2.2. It used Feign for remote connectivity, Zuul for routing, Hystrix for failover, and Spring Security for OAuth. The September 2023 version of Spring Cloud has Spring Cloud OpenFeign for remote connectivity, Spring Cloud Gateway for routing, and Resilience4j for fault tolerance.
Okta also now has an Okta Spring Boot starter. I didn't use it in my first experiment, but I'm a big fan of it after the last few years! It dramatically simplifies configuration and makes securing your apps with OAuth 2.0 and OIDC easy. It's a thin wrapper around Spring Security's resource server, OAuth client, and OIDC features. Not only that, but it works with Okta Workforce Identity, Okta Customer Identity (aka Auth0), and even Keycloak.
I created all of these applications using start.spring.io's REST API and HTTPie.
https start.spring.io/starter.tgz bootVersion==3.2.0 \ artifactId==discovery-service name==eureka-service \ dependencies==cloud-eureka-server baseDir==discovery-service | tar -xzvf - https start.spring.io/starter.tgz bootVersion==3.2.0 \ artifactId==car-service name==car-service baseDir==car-service \ dependencies==actuator,cloud-eureka,data-jpa,data-rest,postgresql,web,validation,devtools,docker-compose,okta | tar -xzvf - https start.spring.io/starter.tgz bootVersion==3.2.0 \ artifactId==api-gateway name==api-gateway baseDir==api-gateway \ dependencies==cloud-eureka,cloud-feign,data-rest,web,okta | tar -xzvf -
You might notice the
api-gateway
project doesn't have cloud-gateway
as a dependency. That's because I started without it and didn't add it until I needed to proxy requests by path.In the code listings below, all
package
and import
statements have been removed for brevity. You can find the complete source code in the auth0-java-microservices-examples repository.Service Discovery with Netflix Eureka
The
discovery-service
is configured the same as you would most Eureka servers. It has an @EnableEurekaServer
annotation on its main class and properties that set its port and turn off discovery.server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false
The
car-service
and api-gateway
projects are configured similarly. Both have a unique name defined, and car-service
is configured to run on port 8090
so it doesn't conflict with 8080
:# car-service/src/main/resources/application.properties server.port=8090 spring.application.name=car-service
# api-gateway/src/main/resources/application.properties spring.application.name=api-gateway
@EnableDiscoveryClient
annotates the main class in both car service and API gateway.Build a Java Microservice with Spring Data REST
The
car-service
provides a REST API that lets you CRUD (Create, Read, Update, and Delete) cars. It creates a default set of cars when the application loads using an ApplicationRunner
bean:// car-service/src/main/java/com/example/carservice/CarServiceApplication.java @EnableDiscoveryClient @SpringBootApplication public class CarServiceApplication { public static void main(String[] args) { SpringApplication.run(CarServiceApplication.class, args); } @Bean ApplicationRunner init(CarRepository repository) { repository.deleteAll(); return args -> { Stream.of("Ferrari", "Jaguar", "Porsche", "Lamborghini", "Bugatti", "AMC Gremlin", "Triumph Stag", "Ford Pinto", "Yugo GV").forEach(name -> { repository.save(new Car(name)); }); repository.findAll().forEach(System.out::println); }; } }
The
CarRepository
interface makes persisting and fetching cars from the database easy:// car-service/src/main/java/com/example/carservice/data/CarRepository.java @RepositoryRestResource public interface CarRepository extends JpaRepository<Car, Long> { }
The
Car
class is a simple JPA entity with an id
and name
property. Spring Boot will see PostgreSQL on its classpath and autoconfigure connectivity. A compose.yaml
file exists in the root directory to start a PostgreSQL instance using Docker Compose:services: postgres: image: 'postgres:latest' environment: - 'POSTGRES_DB=mydatabase' - 'POSTGRES_PASSWORD=secret' - 'POSTGRES_USER=myuser' ports: - '5432'
Spring Boot added Docker Compose support in version 3.1. This means that if you add the following dependency to your
build.gradle
, it'll look for a compose.yaml
(or docker-compose.yaml
) file in the root directory and start it when you run ./gradlew bootRun
:developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
Finally, the
application.properties
has a setting to create the database automatically:spring.jpa.hibernate.ddl-auto=update
Connect to Java Microservices with Spring Cloud OpenFeign
Next, I configured OpenFeign in the
api-gateway
project to connect to the car service and its /cars
endpoint. Then, I mapped a Car
record to the JSON that's returned. I exposed it as a /cool-cars
endpoint:// api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } } record Car(String name) { } @FeignClient("car-service") interface CarClient { @GetMapping("/cars") CollectionModel<Car> readCars(); } @RestController class CoolCarController { private final CarClient carClient; public CoolCarController(CarClient carClient) { this.carClient = carClient; } @GetMapping("/cool-cars") public Collection<Car> coolCars() { return carClient.readCars() .getContent() .stream() .filter(this::isCool) .collect(Collectors.toList()); } private boolean isCool(Car car) { return !car.name().equals("AMC Gremlin") && !car.name().equals("Triumph Stag") && !car.name().equals("Ford Pinto") && !car.name().equals("Yugo GV"); } }
This worked great, but I still wanted to proxy
/home
to the downstream car service.Add Routing with Spring Cloud Gateway
When I first wrote this tutorial with Spring Boot 3.1 and Spring Cloud 2022.0.4, Spring Cloud Gateway only had a WebFlux API. Since Spring Cloud 2023.0.0, it has a Spring MVC API too! This means you can use it with Spring MVC or Spring WebFlux.
Proxy Requests by Path with Spring MVC
I added
spring-cloud-starter-gateway-mvc
as a dependency to the api-gateway
project and added the following to a new api-gateway/src/main/resources/application.yml
file:spring: cloud: gateway: discovery: locator: enabled: true mvc: routes: - id: car-service uri: lb://car-service predicates: - Path=/home/**
With this configuration, I could access the car service directly at
http://localhost:8090/cars
and through the gateway at http://localhost:8080/cool-cars
.To add failover with Spring Cloud Circuit Breaker, I added it as a dependency:
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
Then, I enabled it in
application.properties
:spring.cloud.openfeign.circuitbreaker.enabled=true
I updated the
CarClient
interface in ApiGatewayApplication
to have a fallback that returns an empty list of cars if the car service is unavailable.@FeignClient(name = "car-service", fallback = Fallback.class) interface CarClient { @GetMapping("/cars") CollectionModel<Car> readCars(); } @Component class Fallback implements CarClient { @Override public CollectionModel<Car> readCars() { return CollectionModel.empty(); } }
Proxy Requests by Path with WebFlux
Getting everything to work with Spring MVC and Spring Cloud Gateway didn't take long. Using Spring WebFlux required a bit more work.
I immediately discovered that adding
spring-cloud-starter-gateway
as a dependency caused issues. First, I had Spring MVC in my classpath, and Spring Cloud Gateway uses WebFlux. WebFlux recommends using WebClient over Feign. I decided to switch to WebClient.I had to remove the following dependencies from my original
api-gateway
project:implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
And add Spring Cloud Gateway with Resilience4j dependencies:
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j' implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
Then, I moved
CoolCarController
to its own class and re-implemented it with WebClient:// api-gateway/src/main/java/com/example/apigateway/web/CoolCarController.java @RestController class CoolCarController { Logger log = LoggerFactory.getLogger(CoolCarController.class); private final WebClient.Builder webClientBuilder; private final ReactiveCircuitBreaker circuitBreaker; public CoolCarController(WebClient.Builder webClientBuilder, ReactiveCircuitBreakerFactory circuitBreakerFactory) { this.webClientBuilder = webClientBuilder; this.circuitBreaker = circuitBreakerFactory.create("circuit-breaker"); } record Car(String name) { } @GetMapping("/cool-cars") public Flux<Car> coolCars() { return circuitBreaker.run( webClientBuilder.build() .get().uri("http://car-service/cars") .retrieve().bodyToFlux(Car.class) .filter(this::isCool), throwable -> { log.warn("Error making request to car service", throwable); return Flux.empty(); }); } private boolean isCool(Car car) { return !car.name().equals("AMC Gremlin") && !car.name().equals("Triumph Stag") && !car.name().equals("Ford Pinto") && !car.name().equals("Yugo GV"); } }
In order to inject the
WebClient.Builder
, I had to create a WebClientConfiguration
class:// api-gateway/src/main/java/com/example/apigateway/config/WebClientConfiguration.java @Configuration public class WebClientConfiguration { @Bean @LoadBalanced public WebClient.Builder webClientBuilder() { return WebClient.builder(); } }
In the
car-service
project, I had to switch from using Spring Data REST to handling it with a @RestController
and @GetMapping
annotation. I removed the @RepositoryRestResource
annotation from CarRepository
and added a CarController
class:// car-service/src/main/java/com/example/carservice/web/CarController.java @RestController class CarController { private final CarRepository repository; public CarController(CarRepository repository) { this.repository = repository; } @GetMapping("/cars") public List<Car> getCars() { return repository.findAll(); } }
NOTE: I did try to use Spring HATEOAS but ran into an issue when using it with the Okta Spring Boot starter.
To proxy
/home
to the downstream microservice, I added a api-gateway/src/main/resources/application.yml
file to configure Spring Cloud Gateway to enable service discovery and specify routes:spring: cloud: gateway: discovery: locator: enabled: true routes: - id: car-service uri: lb://car-service predicates: - Path=/home/**
At this point, I could access the car service directly at
http://localhost:8090/cars
and through the gateway at http://localhost:8080/cool-cars
.Secure Spring Boot Microservices with OAuth 2.0 and OIDC
To configure the Okta Spring Boot starter, there are a few properties in the
api-gateway
project's application.properties
file:okta.oauth2.issuer=${OKTA_OAUTH2_ISSUER} okta.oauth2.client-id=${OKTA_OAUTH2_CLIENT_ID} okta.oauth2.client-secret=${OKTA_OAUTH2_CLIENT_SECRET} okta.oauth2.audience=${OKTA_OAUTH2_AUDIENCE}
The
car-service
is configured as an OAuth resource server and has the following properties in its application.properties
file:okta.oauth2.issuer=${OKTA_OAUTH2_ISSUER} okta.oauth2.audience=${OKTA_OAUTH2_AUDIENCE}
The variables are read from the
.env
file in each project's root directory.Fetch an Access Token as a JWT
When I first got things working, I was able to log in to the gateway, but when I tried to connect to the downstream microservice, it said the JWT was invalid. For this reason, I added a
/print-token
endpoint to the gateway that prints the access token to the console. NOTE: The code in this section is for the WebFlux version. The MVC version is in the next section.
// api-gateway/src/main/java/com/example/apigateway/web/HomeController.java @RestController class HomeController { @GetMapping("/") public String howdy(@AuthenticationPrincipal OidcUser user) { return "Hello, " + user.getFullName(); } @GetMapping("/print-token") public String printAccessToken(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) { var accessToken = authorizedClient.getAccessToken(); System.out.println("Access Token Value: " + accessToken.getTokenValue()); System.out.println("Token Type: " + accessToken.getTokenType().getValue()); System.out.println("Expires At: " + accessToken.getExpiresAt()); return "Access token printed"; } }
Using jwt.io, I verified that it wasn't a valid JWT. I thought about trying to implement Spring Security's opaque token support, but discovered Auth0 doesn't have an
endpoint. This makes it impossible to use opaque tokens with Auth0./instropection
The good news is I figured out a workaround! If you pass a valid
audience
parameter to Auth0, you'll get a JWT for the access token. I logged an issue to improve the Okta Spring Boot starter and added a SecurityConfiguration
class to solve the problem in the meantime.// api-gateway/src/main/java/com/example/apigateway/config/SecurityConfiguration.java @Configuration public class SecurityConfiguration { @Value("${okta.oauth2.audience:}") private String audience; private final ReactiveClientRegistrationRepository clientRegistrationRepository; public SecurityConfiguration(ReactiveClientRegistrationRepository clientRegistrationRepository) { this.clientRegistrationRepository = clientRegistrationRepository; } @Bean public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception { http .authorizeExchange(authz -> authz .anyExchange().authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationRequestResolver( authorizationRequestResolver(this.clientRegistrationRepository) ) ); return http.build(); } private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver( ReactiveClientRegistrationRepository clientRegistrationRepository) { var authorizationRequestResolver = new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository); authorizationRequestResolver.setAuthorizationRequestCustomizer( authorizationRequestCustomizer()); return authorizationRequestResolver; } private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() { return customizer -> customizer .additionalParameters(params -> params.put("audience", audience)); } }
To make Spring Cloud Gateway pass the access token downstream, I added
TokenRelay
to its default filters in application.yml
:spring: cloud: gateway: discovery: locator: enabled: true default-filters: - TokenRelay routes: ...
I updated the
WebClientConfiguration
class to configure WebClient
to include the access token with its requests:// api-gateway/src/main/java/com/example/apigateway/config/WebClientConfiguration.java @Configuration public class WebClientConfiguration { @Bean @LoadBalanced public WebClient.Builder webClientBuilder(ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) { var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); oauth.setDefaultClientRegistrationId("okta"); return WebClient .builder() .filter(oauth); } }
Spring Cloud Gateway MVC and OAuth 2.0
To get the Spring Cloud Gateway MVC example working with OAuth 2.0, I had to add a
SecurityConfiguration
class to pass an audience
parameter to Auth0:// api-gateway/src/main/java/com/example/apigateway/config/SecurityConfiguration.java @Configuration public class SecurityConfiguration { @Value("${okta.oauth2.audience:}") private String audience; private final ClientRegistrationRepository clientRegistrationRepository; public SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository) { this.clientRegistrationRepository = clientRegistrationRepository; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authorization -> authorization .authorizationRequestResolver( authorizationRequestResolver(this.clientRegistrationRepository) ) ) ); return http.build(); } private OAuth2AuthorizationRequestResolver authorizationRequestResolver( ClientRegistrationRepository clientRegistrationRepository) { DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorization"); authorizationRequestResolver.setAuthorizationRequestCustomizer( authorizationRequestCustomizer()); return authorizationRequestResolver; } private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() { return customizer -> customizer .additionalParameters(params -> params.put("audience", audience)); } }
Spring Cloud Gateway MVC 2023.0.0 doesn't allow you to configure a
filter in YAML, so I added a TokenRelay
RouterFunction
bean to add it.// api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java public class ApiGatewayApplication { @Bean public RouterFunction<ServerResponse> gatewayRouterFunctionsLoadBalancer() { return route("car-service") .route(path("/home/**"), http()) .filter(lb("car-service")) .filter(tokenRelay()) .build(); } public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
Thanks for the help with this code, Spencer Gibb! 🙌
The updated
application.yml
file looks as follows after removing its mvc
configuration.spring: cloud: gateway: discovery: locator: enabled: true
The last thing I needed to configure was OAuth integration for OpenFeign. I added the following properties to
application.properties
:spring.cloud.openfeign.oauth2.enabled=true spring.cloud.openfeign.oauth2.clientRegistrationId=okta
I removed the
spring.cloud.openfeign.circuitbreaker.enabled
property because I could not get it to work with Spring MVC. If you know how to make it work, please let me know in the comments!Spring Boot Microservices and Refresh Tokens
In my previous Spring Boot 2.2 example, I couldn't get refresh tokens to work. I was able to get them to work this time! I changed the default scopes in
api-gateway
to request a refresh token using the offline_access
scope:# .env OKTA_OAUTH2_AUDIENCE=https://fast-expiring-api OKTA_OAUTH2_SCOPES=openid,profile,email,offline_access
And added a property to
application.properties
to read it:# src/main/resources/application.properties okta.oauth2.scopes=${OKTA_OAUTH2_SCOPES}
Then, I created an API in Auth0 called
fast-expiring-api
and set it to expire in 30 seconds:auth0 apis create --name fast-expiring --identifier https://fast-expiring-api \ --token-lifetime 30 --offline-access --no-input
If you do the same, you can restart the API gateway and go to
http://localhost:8080/print-token
to see your access token. You can copy the expired time to timestamp-converter.com to see when it expires in your local timezone. Wait 30 seconds and refresh the page. You'll see a request for a new token and an updated Expires At
timestamp in your terminal.The Okta Spring Boot starter and Keycloak
If you find yourself in a situation where you don't have an internet connection, it can be handy to run Keycloak locally in a Docker container. Since the Okta Spring Boot starter is a thin wrapper around Spring Security, it works with Keycloak, too.
In my experience, Spring Security's OAuth support works with any OAuth 2.0-compliant server. The Okta Spring Boot starter does validate the issuer to ensure it's an Okta URL, so you must use Spring Security's properties instead of the
okta.oauth2.*
properties when using Keycloak.An easy way to get a pre-configured Keycloak instance is to use JHipster's
jhipster-sample-app-oauth2
application. It gets updated with every JHipster release. You can clone it with the following command:git clone https://github.com/jhipster/jhipster-sample-app-oauth2.git --depth=1 cd jhipster-sample-app-oauth2
Then, start its Keycloak instance:
docker compose -f src/main/docker/keycloak.yml up -d
You can configure the
api-gateway
to use Keycloak by removing the okta.oauth2.*
properties and using Spring Security's in application.properties
:spring.security.oauth2.client.provider.okta.issuer-uri=http://localhost:9080/realms/jhipster spring.security.oauth2.client.registration.okta.client-id=web_app spring.security.oauth2.client.registration.okta.client-secret=web_app spring.security.oauth2.client.registration.okta.scope=openid,profile,email,offline_access
The
car-service
requires similar changes in its application.properties
file:spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9080/realms/jhipster spring.security.oauth2.resourceserver.jwt.audiences=account
Restart both apps, open
http://localhost:8080
, and you'll be able to log in with Keycloak:Use
admin
/admin
for credentials, and you can access http://localhost:8080/cool-cars
as you did before:Stay secure with Spring Boot and Spring Cloud!
I hope you liked this tour of how to build Java microservice architectures with Spring Boot and Spring Cloud. You learned how to build everything with minimal code and then configure it to be secure with Spring Security, OAuth 2.0, OIDC, and Auth0 by Okta.
You can find all the code shown in this tutorial on GitHub in the @oktadev/auth0-java-microservices-examples repository. The OpenFeign example with Spring MVC is in the
spring-boot-gateway-mvc
directory. The Spring Cloud Gateway with WebFlux is in spring-boot-gateway-webflux
. The Keycloak example is in the keycloak
branch.If you liked this post, you might enjoy these related posts:
- Deploy Secure Spring Boot Microservices on Amazon EKS Using Terraform and Kubernetes
- Get started with Spring Boot and Auth0
- Build a Beautiful CRUD App with Spring Boot and Angular
- Get Started with Jetty, Java, and OAuth
We've also published some new labs about securing Spring Boot in our Developer Center. They're great if you like to learn by doing!
- Authorization in Spring Boot
- Authentication in Spring Boot
- Role Based Access Control in Spring Boot
- Build and Secure Spring Boot Microservices
Please follow us on Twitter @oktadev and subscribe to our YouTube channel for more Spring Boot and microservices knowledge.
You can also sign up for our developer newsletter to stay updated on everything Identity and Security.
About the author
Matt Raible
Developer Advocate