Spring Boot
Keycloak
Role Authentication
Spring Security
OAuth2

Enable role authentication with spring boot security and keycloak?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

With Spring Boot and Keycloak, successful login only proves authentication. Role-based authorization works only when the roles issued by Keycloak are present in the token and are mapped into Spring Security authorities in a format such as ROLE_ADMIN.

The End-to-End Role Flow

For role checks to work, several pieces must line up:

  1. Keycloak authenticates the user.
  2. Keycloak includes realm roles or client roles in the token.
  3. Spring Security reads the JWT.
  4. Spring converts token role claims into granted authorities.
  5. Authorization rules such as hasRole("ADMIN") evaluate those authorities.

If step four is missing or wrong, the user appears authenticated but still gets 403 Forbidden on protected routes.

A Good Spring Security Baseline

In modern Spring Boot applications, the clean baseline is usually JWT resource-server configuration.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
4import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5import org.springframework.security.web.SecurityFilterChain;
6
7@Configuration
8@EnableMethodSecurity
9public class SecurityConfig {
10
11    @Bean
12    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
13        http
14            .authorizeHttpRequests(auth -> auth
15                .requestMatchers("/admin/**").hasRole("ADMIN")
16                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
17                .anyRequest().authenticated()
18            )
19            .oauth2ResourceServer(oauth -> oauth.jwt());
20
21        return http.build();
22    }
23}

This handles authentication, but it still assumes the JWT roles are mapped correctly.

Mapping Keycloak Roles Explicitly

Keycloak often stores realm roles in realm_access.roles and client roles in resource_access.<client>.roles. Spring Security does not automatically turn those nested claims into the exact authorities your application expects, so a custom converter is often the simplest solution.

java
1import java.util.Collection;
2import java.util.List;
3import java.util.Map;
4import java.util.stream.Stream;
5import org.springframework.core.convert.converter.Converter;
6import org.springframework.security.core.GrantedAuthority;
7import org.springframework.security.core.authority.SimpleGrantedAuthority;
8import org.springframework.security.oauth2.jwt.Jwt;
9
10public class KeycloakAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
11
12    @Override
13    public Collection<GrantedAuthority> convert(Jwt jwt) {
14        return Stream.concat(
15                extractRealmRoles(jwt).stream(),
16                extractClientRoles(jwt, "myclient").stream()
17            )
18            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
19            .map(a -> (GrantedAuthority) a)
20            .toList();
21    }
22
23    private List<String> extractRealmRoles(Jwt jwt) {
24        Map<String, Object> realmAccess = jwt.getClaim("realm_access");
25        if (realmAccess == null) return List.of();
26        Object roles = realmAccess.get("roles");
27        return roles instanceof List<?> list ? list.stream().map(Object::toString).toList() : List.of();
28    }
29
30    private List<String> extractClientRoles(Jwt jwt, String clientId) {
31        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
32        if (resourceAccess == null) return List.of();
33        Object client = resourceAccess.get(clientId);
34        if (!(client instanceof Map<?, ?> map)) return List.of();
35        Object roles = map.get("roles");
36        return roles instanceof List<?> list ? list.stream().map(Object::toString).toList() : List.of();
37    }
38}

Wiring the Converter Into Spring

Once the converter exists, attach it to the JWT authentication flow.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
3
4@Bean
5JwtAuthenticationConverter jwtAuthenticationConverter() {
6    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
7    converter.setJwtGrantedAuthoritiesConverter(new KeycloakAuthoritiesConverter());
8    return converter;
9}

Now the token claims can become authorities that your route and method security understand.

Method-Level Authorization

After the mapping is correct, method-level authorization becomes simple.

java
1import org.springframework.security.access.prepost.PreAuthorize;
2import org.springframework.web.bind.annotation.GetMapping;
3import org.springframework.web.bind.annotation.RestController;
4
5@RestController
6public class AdminController {
7
8    @PreAuthorize("hasRole('ADMIN')")
9    @GetMapping("/admin/ping")
10    public String ping() {
11        return "ok";
12    }
13}

If this still fails, inspect the token first. Many debugging sessions go in the wrong direction because the developer changes Spring configuration before confirming that the role claim is present in the JWT at all.

Common Pitfalls

A common mistake is assuming that authentication automatically implies role mapping. It does not. A valid token can still contain no usable authorities for Spring.

Another issue is forgetting that hasRole("ADMIN") expects an authority shaped like ROLE_ADMIN. If you map raw role names without the prefix, authorization checks will fail even though the right role data exists.

Teams also mix older Keycloak-specific adapters with modern Spring resource-server configuration unnecessarily. In most current setups, the simpler JWT resource-server path is easier to reason about.

Summary

  • Authentication and authorization are separate steps in a Spring Boot plus Keycloak integration.
  • Keycloak must emit roles in the token, and Spring must map them into authorities.
  • JWT resource-server configuration is a good modern baseline.
  • A custom converter is often the cleanest way to map nested Keycloak role claims.
  • When access fails, inspect the actual JWT before changing controller or annotation code.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.