Spring Boot 4 Microservices Project: Home Energy Tracker - JWT & Keycloak OAuth2 (Part 12)
This chapter covers securing a microservices architecture in Spring Boot 4 using Keycloak as an OpenID Connect (OIDC) identity provider and OAuth2 Resource Server configuration inside the Spring Cloud API Gateway. We explore how to eliminate hardcoded credentials, issue JSON Web Tokens (JWTs), validate cryptographic signatures using JSON Web Key Sets (JWKS), and protect downstream microservices.
1. Why Do We Need API Security in Microservices?
In our current microservices architecture, the API Gateway serves as the single entry point for all external client traffic. However, without security measures in place, any client possessing the base URL can bypass proper access controls and directly hit downstream services (/users, /devices, etc.).
- The Exposure Problem: Open endpoints leave backend systems vulnerable to unauthorized data access, abuse, and malicious payloads.
- The Authentication Burden: Individual microservices should not be responsible for managing user passwords, issuing sessions, or maintaining user databases.
- Centralized Identity Management: We need a dedicated identity provider (IdP) to handle user lifecycles, authentication flows, roles, and cryptographic token issuance.
Client (Postman / Browser)
|
| sends request with raw URL (No Security)
v
API Gateway
|
| forwards unverified requests directly
v
Downstream Microservices (User Service, Device Service, etc.)
2. Core Mental Model: The Keycloak & JWT Analogy
To understand how Keycloak and JWT authentication work together, consider a secure corporate office building:
Real-Life Analogy: Imagine an exclusive corporate headquarters. Instead of every single room inside the building checking employee IDs and cross-referencing a master HR database, there is a central Security Reception Desk (Keycloak) at the front entrance.
When an employee arrives, they present their credentials once to Reception. Reception verifies who they are and hands them a Signed Visitor Badge with a Hologram (JWT).
As the employee moves through the building, individual room guards do not call HR. They simply inspect the hologram on the badge to confirm it was officially issued by front reception and hasn't expired.
How This Maps to Our Architecture:
- Keycloak acts as the Central Security Desk (Identity and Access Management tool). It handles user logins, passwords, and issues cryptographic JWT tokens.
- Postman / Client acts as the visitor. It requests a token from Keycloak using client credentials.
- API Gateway acts as the room guard configured as an OAuth2 Resource Server. It validates incoming JWT signatures using public keys fetched from Keycloak without ever handling user passwords.
3. Introducing Keycloak via Docker Compose
To run Keycloak locally without manual installation hassles, we spin it up using Docker containers alongside a dedicated MySQL backing database for storing realm configurations and user metadata.
Docker Compose Service Definitions
Add the following containers to your docker-compose.yml file:
version: '3.8'
services:
keycloak-db:
image: mysql:8.0
container_name: keycloak-db
environment:
MYSQL_DATABASE: keycloak
MYSQL_USER: keycloak
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: rootpassword
ports:
- "3306:3306"
restart: always
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak
command: start-dev
environment:
KC_DB: mysql
KC_DB_URL: jdbc:mysql://keycloak-db:3306/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- "8091:8080"
depends_on:
- keycloak-db
restart: always
Run the container stack in detached mode:
docker compose up -d
4. Keycloak Console Configuration: Realms and Clients
Once Docker starts up Keycloak, access the administrative dashboard at http://localhost:8091. Log in using the admin credentials (admin / admin).
A. Creating a Custom Realm
A Realm manages an isolated set of users, credentials, roles, and groups. Realms are completely independent of one another.
- Click on the dropdown in the top-left corner and select Create Realm.
- Name the realm:
home energy tracker security realm. - Ensure Enabled is toggled on, then click Create.
B. Configuring an OAuth2 Client
A Client represents an application or service that requests user authentication. In our test workflow, Postman acts as the client requesting tokens.
- Navigate to Clients on the left menu, then click Create client.
- Set Client ID:
home energy tracker client. Click Next. - Configure client authentication settings:
- Client authentication:
On(ensures confidential client verification using a client secret). - Standard flow:
Off(disables authorization code flow for browser redirects). - Direct access grants:
Off. - Service accounts roles:
On(enables machine-to-machine client credentials grant).
- Client authentication:
- Click Next, leave optional settings default, and click Save.
- Navigate to the Credentials tab of your newly created client and copy the generated Client Secret for later use in Postman.
5. Adding Spring Security & OAuth2 Resource Server Dependencies
Open the pom.xml file of your API Gateway microservice and add the Spring Boot Starter OAuth2 Resource Server dependency to handle JWT validation:
<dependencies>
<!-- Spring Boot Starter OAuth2 Resource Server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Other existing dependencies (Gateway, Eureka Client, etc.) -->
</dependencies>
6. Configuring Application Properties for JWT Validation
To enable the API Gateway to decode and verify JWT tokens issued by Keycloak, configure the JWK (JSON Web Key) Set URI and Issuer URI in src/main/resources/application.properties.
Locating Your Keycloak Endpoints
- In the Keycloak admin console under your realm settings, click on OpenID Endpoint Configuration.
- Locate the following values:
- JWKS URI (
jwks_uri): Provides the public keys used to verify token cryptographic signatures. - Issuer (
issuer): Identifies who generated the token.
- JWKS URI (
application.properties
# Server Port
server.port: 8080
# Keycloak Security Configurations
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8091/realms/home-energy-tracker-security-realm/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8091/realms/home-energy-tracker-security-realm
# Custom Property for Excluded Security Paths
security.excluded-paths=/actuator/**
Concept: When Keycloak issues a JWT, it signs the payload using a private key. The API Gateway downloads the matching public keys from the
jwk-set-uriendpoint to cryptographically verify the signature without needing access to Keycloak's private keys or user passwords.
7. Implementing Spring Security Filter Chain Configuration
Create a config package inside the API Gateway project and write the SecurityConfig class. This configuration intercepts incoming HTTP requests, enforces authentication via OAuth2 resource server resource validation, and permits public health endpoints like Actuator.
package com.example.gateway.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Value("${security.excluded-paths}")
private String[] excludedPaths;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.authorizeExchange(exchange -> exchange
// Permit unauthenticated access to actuator and health endpoints
.pathMatchers(excludedPaths).permitAll()
// Require authentication for all other requests
.anyExchange().authenticated()
)
// Enable OAuth2 Resource Server support using JWT validation
.oauth2ResourceServer(oauth2 -> oauth2.jwt(org.springframework.security.config.Customizer.withDefaults()))
.build();
}
}
8. Step-by-Step Flow Walkthrough: Acquiring and Using a JWT Token
With our configuration complete, let's trace how a client requests data through the secured API Gateway using Postman.
Step 1: Requesting Data Without a Token (Failure)
If you send an HTTP GET request to http://localhost:8080/users without authentication:
Step 2: Generating an Access Token in Postman
- Open Postman and navigate to the Authorization tab of your request.
- Select OAuth 2.0 as the type.
- Configure a new Token configuration:
- Grant Type:
Client Credentials - Access Token URL:
http://localhost:8091/realms/home-energy-tracker-security-realm/protocol/openid-connect/token - Client ID:
home energy tracker client - Client Secret: (Paste the secret copied from Keycloak client credentials tab)
- Client Authentication:
Send as Basic Auth header
- Grant Type:
- Click Get New Access Token. Postman exchanges the client credentials with Keycloak and receives a signed JWT Bearer token.
- Click Proceed to attach the token to your request headers automatically.
Postman
|
| POST /token (Client ID + Client Secret)
v
Keycloak Token Endpoint
|
| Validates credentials and generates signed JWT
v
Postman receives JWT Bearer Token
Step 3: Sending Authenticated Requests (Success)
With the Bearer token attached:
9. Architectural Reality: API Gateway vs. Downstream Services
A common point of confusion in microservice security is whether downstream services (like the User Service or Device Service) also require individual security filters when accessed directly.
Network Topology Comparison
- Local Development Environment: In local testing, developers often spin up individual microservices on ports like
8081,8082, etc. If unconfigured, callinghttp://localhost:8081/usersdirectly might still succeed because the service runs in isolation without network-level firewalls. - Production Environment: In a robust production architecture, all downstream microservices reside in an isolated private virtual network (VPC). The API Gateway sits in the public network behind a load balancer as the sole entry point.
Summary and Key Takeaways
- Centralized Identity Management: Keycloak eliminates the need to build custom login pages, password hashing, and user storage from scratch by serving as an open-source IAM tool.
- Realms and Clients: Realms provide logical isolation for sets of users and credentials, while Clients represent applications (like Postman or frontend portals) requesting authentication.
- OAuth2 Resource Server: Spring Boot's
spring-boot-starter-oauth2-resource-servereffortlessly configures reactive web applications to validate incoming JWT bearer tokens. - JWKS Cryptographic Verification: Instead of hardcoding keys, the API Gateway dynamically fetches public keys from Keycloak's JSON Web Key Set (JWKS) endpoint to verify token signatures securely.
- Path Exclusions (
permitAll): Public endpoints such as Spring Boot Actuator health checks can be safely exempted from security filter chains usingpathMatchers. - Gateway Pattern Enforcement: In production, security enforcement should be centralized at the API Gateway layer, placing downstream microservices behind private networks where direct access is blocked.