avataralexlionnel

Summary

The content outlines the process of securing inter-service communication in a microservices architecture using Spring Boot 3.2, Keycloak, and WebClient with OAuth2 client credentials flow.

Abstract

The article discusses the implementation of an OAuth2 client in a microservice application using Spring Boot 3.2, Keycloak, and WebClient to ensure secure authentication for inter-service requests. It details the steps to configure a Keycloak realm and client for each microservice, enable client authentication, and set up the Spring Boot application with the necessary dependencies and configurations. The article also provides code snippets for configuring Spring Security and creating a WebClient bean that automatically requests a token for service-to-service communication. The WebClient is favored over RestClient due to its compatibility with the EchangeFilterFunction for applying security filters.

Opinions

  • The author emphasizes the importance of securing inter-service communication in a microservices architecture.
  • The preference for WebClient over RestClient is clear, as the latter is not fully compatible with EchangeFilterFunction for applying security filters.
  • The author suggests that using Keycloak as an identity provider and Spring Boot's built-in OAuth2 client support simplifies the security configuration for microservices.
  • The use of the OAuth2 client credentials grant type is recommended for service-to-service authentication, as it aligns with the stateless nature of microservices.

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

  1. 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

Microservices
Spring Boot
Keycloak
Oauth2
Client Credentials Flow
Recommended from ReadMedium