Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save PavithMadusara/eb4f9fb828fb72fd2b9bec0824913952 to your computer and use it in GitHub Desktop.
Save PavithMadusara/eb4f9fb828fb72fd2b9bec0824913952 to your computer and use it in GitHub Desktop.
Spring Boot Microservice M2M and Token Relay with Keycloak

Spring Boot Security Configuration for Microservices with Keycloak for Token Relay and OAuth2

Secure Microservice with Keycloak (+ Token Relay)

After this steps, you can use KeycloakRestTemplate to access the secured microservice (Relay the Token). However, this only works if the request is initialized by a user. See OAuth2 WebClient Configuration for Sending requests from Microservice to Microservice.

  • Add following dependencies to your project
<depencencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-boot-starter</artifactId>
        <version>16.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-security-adapter</artifactId>
        <version>16.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</depencencies>
  • Use environment variables or add properties to the application.properties file to configure the Keycloak server
keycloak:
  realm: ${AUTH_REALM:<realm-name>}
  resource: ${AUTH_RESOURCE:<client-id>}
  auth-server-url: ${AUTH_SERVER:<server-url>}
  credentials.secret: ${AUTH_SECRET:<keyclaok secret>}
  bearer-only: true
  • Create Keycloak Configuration Class
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory;
import org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.web.client.DefaultResponseErrorHandler;

/**
 * @author Pavith Madusara
 * Created at 08-Feb-2022
 * Wednesday at 2:24 PM
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class KeyCloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public KeycloakClientRequestFactory keycloakClientRequestFactory;

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public KeycloakRestTemplate keycloakRestTemplate() {
        KeycloakRestTemplate restTemplate = new KeycloakRestTemplate(keycloakClientRequestFactory);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .anyRequest()
                .permitAll();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }
}
  • Add Config Resolver to Application Class
public class MyApplication{

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
    
    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}
  • RestTemplate and KeycloakRestTemplate Usage
@Service
public class MediaService {

    private final KeycloakRestTemplate restTemplate;
    @Value("${gateway.rootUrl}")
    private String gatewayUrl;

    public MediaService(KeycloakRestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public BucketEntityDTO getBucketEntity(Long id) {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(gatewayUrl + "/media/api/v1/bucket-entities/{id}", BucketEntityDTO.class, id);
    }

    public GalleryDTO createGallery(GalleryDTO galleryDTO) {
        return restTemplate.postForObject(gatewayUrl + "/media/api/v1/galleries", galleryDTO, GalleryDTO.class);
    }
}

OAuth2 Authenticated WebClient Configuration

  • Add following dependencies to the project
<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>
  • Add following configuration to the application.properties file
spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: ${keycloak.resource}
            client-secret: ${keycloak.credentials.secret}
            authorization-grant-type: client_credentials
        provider:
          keycloak:
            token-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}/protocol/openid-connect/token
  • Create OAuth2 WebClient Configuration Class
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class OAuth2WebClientConfig {
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService clientService)
    {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, clientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("keycloak");
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }
}
  • WebClient Usage
@Service
public class RolesService {
    @Autowired
    private WebClient webClient;
    @Value("${gateway.rootUrl}")
    private String gatewayURL;

    public void createRole(RoleDTO roleDTO) {
        webClient.post()
                .uri(gatewayURL + "/auth/api/v1/roles")
                .bodyValue(roleDTO)
                .retrieve()
                .bodyToMono(RoleDTO.class)
                .block();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment