Keycloak config for Java application with Spring (Java 21)
If you want to secure your Spring microservice endpoints with Keycloak, you are in perfect place. Let’s do this step by step.
First we need is of course to add proper dependencies, I’m using maven:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> </dependencies>
Second, provide some properties for our authorization server connection:
spring: security: oauth2: resourceserver: jwt: issuer-uri: http://localhost:8080/realms/custom-realm jwk-set-uri: http://localhost:8080/realms/custom-realm/protocol/openid-connect/certs
Third and last – create a configuration class. Good practice is to keep your endpoints authorized by default (as provided below).
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.SecurityFilterChain; import java.util.Collection; import java.util.Map; @Configuration @EnableWebSecurity public class KeycloakConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeHttpRequests(registry -> registry // unsecured endpoints .requestMatchers("/example/unsecured") .permitAll() // default authorized .anyRequest() .hasRole("default-roles-custom-realm")) .oauth2ResourceServer(oauth2Configurer -> oauth2Configurer.jwt(jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwt -> { Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access"); Collection<String> roles = realmAccess.get("roles"); var grantedAuthorities = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList(); return new JwtAuthenticationToken(jwt, grantedAuthorities); }))); return httpSecurity.csrf(AbstractHttpConfigurer::disable).build(); } }
Extras
Getting user information
Template for getting user info:
import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class UserUtils { public static String getActualUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!(authentication instanceof AnonymousAuthenticationToken)) { return authentication.getName(); } else { throw new IllegalStateException(); } } }
Working with tokens with curl
Getting access token:
curl -v -X POST http://localhost:8080/realms/custom-realm/protocol/openid-connect/token -H 'Content-Type:application/x-www-form-urlencoded' --data "client_id=custom-client&username=user&password=user&grant_type=password"
Refreshing token:
curl -v --data "grant_type=refresh_token&client_id=custom-client&refresh_token={REFRESH_TOKEN}" http://localhost:8080/realms/custom-realm/protocol/openid-connect/token