Service as Oauth2 client on microservice application, using Spring Boot 3.2, Keycloak and WebClient
Service as Oauth2 client on microservice application, using Spring Boot 3.2, Keycloak and WebClient
When we are dealing with microservice architecture, it is important to ensure that each request between microservice is authenticated. We can use Keycloak as identity provider, and auto configure a WebClient instance to make inter service call with a valid token.
Here is the flow of a request when we a deeling with inter service communication.

Implementing WebClient Oauth2
Stack: Spring boot 3.2.2, Keycloak 23.0.6
- First step is to create a realm for the whole application, and a client per microservice on Keycloak (in microservice architecture, each service has its own keycloak client)
After creating the realm moviebrains
, i created a client catalog-app

After that, make sure you enable Client authentication
and check Service accounts roles
. This configuration allows the client to authenticate and get a token. We are using Oauth2 Client Credentials grant type.

Specify web origins value as the path of spring boot catalog app

You can get the client credentials informations on the Credentials tab

2. Next, we can create a spring boot application and add right dependencies (here i am using Gradle)
Gradle config file build.gradle.kts
plugins {
java
id("org.springframework.boot") version "3.2.3"
id("io.spring.dependency-management") version "1.1.4"
id("org.graalvm.buildtools.native") version "0.9.28"
}
group = "io.albrains.moviebrains"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}
3. In spring config file, add config file for spring security oauth2 client application.yml
. Replace <keycloak-client-secret
with the client secret of catalog-app keycloak client.
server:
port: 9082
servlet:
context-path: /api
spring:
application:
name: profile
security:
oauth2:
client:
registration:
keycloak:
client-id: catalog-app
client-secret: <keycloak-client-secret>
authorization-grant-type: client_credentials
provider:
keycloak:
issuer-uri: http://localhost:18080/realms/moviebrains
user-name-attribute: preferred_username
resource-server:
jwt:
issuer-uri: http://localhost:18080/realms/moviebrains
4. Create the Spring security config file
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
private final JwtAuthConverter jwtAuthConverter;
public WebSecurityConfig(JwtAuthConverter jwtAuthConverter) {
this.jwtAuthConverter = jwtAuthConverter;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Client(withDefaults())
.oauth2ResourceServer(serverConfigurer -> serverConfigurer.jwt(
jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwtAuthConverter)
));
return http.build();
}
}
5. Create a web client bean witch will ask for a token before sending request to another microservice. Note that valid token is saved in memory by default.
@Configuration
public class WebClientInterServiceConfig {
@Bean("microservice")
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("keycloak");
return WebClient.builder()
.filter(oauth2Client)
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
6. Now we can use this Webclient instance to make a call to accounting microservice.
private final WebClient webclient;
public MoviebrainsBookApplication(@Qualifier("microservice") WebClient webclient) {
this.webclient = webclient;
}
private void getPrice() {
PriceResponse response = webclient.get()
.uri("http://localhost:9081/accounting/api/book/{bookId}")
.retrieve()
.bodyToMono(PriceResponse.class)
.block();
System.out.println(response);
}
Using WebClient instead of RestClient
I am using WebClient instead of RestClient because RestClient is not fully compatible with FilterFunction. It is not possible to apply a filter EchangeFilterFunction
to RestClient builder. There is an open issue on this :
Press the clap button if this article was helpful for you.
Follow me on LinkedIn