Enterprise Java

Spring Boot & Keycloak: Role-Based Authorization

Practical Guide to Securing Services with Keycloak and Spring Security

When building microservices or REST APIs, securing endpoints is often one of the first and most critical concerns. Keycloak, an open-source identity and access management solution, pairs seamlessly with Spring Boot to provide authentication and fine-grained role-based authorization using OAuth2 and JWT tokens.

Keycloak and Spring Security logo

In this article, you’ll learn how to secure a Spring Boot resource server with Keycloak, configure role-based access, and apply clean, maintainable authorization constraints.

1. Why Use Keycloak with Spring Boot?

Keycloak offers several advantages over rolling your own OAuth2 server or using a simpler approach like basic auth:

✅ Standards-based OAuth2 and OpenID Connect
✅ Centralized authentication for multiple applications
✅ Fine-grained role mapping and access control
✅ Excellent Spring Security support via JWT

In a microservice environment, this allows consistent, scalable security across services.

2. Overview of the Architecture

In this guide, you’ll set up:

  • A Keycloak server managing realms, clients, and user roles
  • A Spring Boot resource server validating JWTs from Keycloak
  • Role-based authorization at the endpoint level

The authentication flow:

  1. A user logs in through Keycloak.
  2. Keycloak issues a JWT access token containing user roles.
  3. The client (e.g., frontend app) calls your Spring Boot API, attaching the token.
  4. Spring Security validates the token and grants/denies access based on roles.

3. Setting Up Keycloak

First, download and run Keycloak (or use a container):

docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:24.0 start-dev

Then:

  1. Log in to Keycloak Admin Console at http://localhost:8080/admin.
  2. Create a new realm (e.g., demo-realm).
  3. Under Clients, create a client (e.g., springboot-api) with:
    • Client ID: springboot-api
    • Client Protocol: openid-connect
    • Access Type: confidential
    • Enable Service Accounts (if you plan to use client credentials)
  4. Under Roles, create roles like user and admin.
  5. Create users and assign them roles.

Important: Make sure the client has Standard Flow Enabled and Direct Access Grants Enabled if you plan to get tokens via password grant for testing.

4. Spring Boot Resource Server

Let’s create a Spring Boot project with spring-boot-starter-oauth2-resource-server and spring-boot-starter-security.

Add the dependencies in pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

5. Configuring JWT Validation

In application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/demo-realm

Spring Boot will automatically fetch the JWKs from Keycloak’s discovery endpoint and validate tokens.

6. Mapping Roles from JWT

By default, Keycloak includes roles under realm_access.roles. To map them into Spring Security’s authorities, you need a custom converter.

JwtAuthConverter.java:

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class JwtAuthConverter implements Converter<Jwt, Collection<SimpleGrantedAuthority>> {
    @Override
    public Collection<SimpleGrantedAuthority> convert(Jwt jwt) {
        var realmAccess = jwt.getClaimAsMap("realm_access");
        if (realmAccess == null || realmAccess.isEmpty()) {
            return List.of();
        }
        var roles = (List<String>) realmAccess.get("roles");
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
                .collect(Collectors.toList());
    }
}

SecurityConfig.java:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter());

        http
          .authorizeHttpRequests(authorize -> authorize
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
          )
          .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter))
          );
        return http.build();
    }
}

Explanation:

  • /admin/** requires admin role.
  • /user/** allows user or admin.
  • Everything else requires authentication.

7. Example Controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/public")
    public String publicEndpoint() {
        return "Public endpoint - no authentication required";
    }

    @GetMapping("/user/hello")
    public String userHello() {
        return "Hello, user!";
    }

    @GetMapping("/admin/hello")
    public String adminHello() {
        return "Hello, admin!";
    }
}

8. Testing with an Access Token

Use the Keycloak CLI or REST API to obtain a token:

curl \
  -d "client_id=springboot-api" \
  -d "username=testuser" \
  -d "password=testpassword" \
  -d "grant_type=password" \
  "http://localhost:8080/realms/demo-realm/protocol/openid-connect/token"

Then call the API:

curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:8081/user/hello

9. Opinions and Best Practices

Explicit Role Mapping
Always define a custom converter to avoid surprises in how roles are resolved.

Least Privilege Principle
Keep your roles minimal and clear. Avoid overloading admin roles with too many permissions.

Centralized Authorization Logic
If your services grow, consider using method-level annotations like @PreAuthorize("hasRole('ADMIN')") rather than only URL patterns. This keeps security close to the business logic.

Token Expiration and Refresh
Remember that access tokens are short-lived. Use refresh tokens on the client side for longer sessions.

Keep Your Keycloak Version Up to Date
Security vulnerabilities do appear; upgrade regularly.

10. Conclusion

Combining Spring Boot with Keycloak offers a powerful, standards-based approach to authentication and role-based authorization in modern applications. You get the best of both worlds: Keycloak’s enterprise-grade identity management and Spring Security’s mature, customizable security filters.

With the example setup above, you now have a robust foundation for securing your services. Feel free to tailor role mappings, authorization rules, and token lifecycles to your needs.

Further Reading

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button