Securing REST API with Spring Security, OAuth2, JWT & Keycloak

Securing APIs is a critical part of building resilient microservice architectures. In service‑to‑service scenarios, using user-based authentication falls short.

Instead, service accounts and OAuth2 client credentials provide a cleaner, more scalable alternative without hardcoding secrets or credentials.

In this article, we’ll explore:

  • A Java based secure REST API using Keycloak and JWT
  • A stateless OAuth2 flow between a service client and the API server
  • Role‑based access control with custom JWT role mapping

You can find the full reference implementation in the secure-rest-api module of my RealWorld Java Solutions repository.

I’ll walk through the theory, essential code, and security rationale without recreating the entire project here.

The Use Case

Imagine you have a background service (e.g., an inventory sync worker) that must invoke a protected /products endpoint on another service. There is no interactive user, just two services requiring secure communication.

Security Goals

  • Enforce Zero Trust: every request must be authenticated
  • Use client credentials grant: no user data, just service identity
  • Provide role-based access so only specific services can perform certain actions

Sequence Flow

sequenceDiagram
participant Client
participant Keycloak
participant API
Client->>Keycloak: POST /token (client_credentials)
Keycloak-->>Client: JWT access token
Client->>API: GET /products (Authorization: Bearer …)
API->>Keycloak: Validate JWT via JWKS
API-->>Client: 200 OK or 403 Forbidden

Why Keycloak?

Keycloak is an open-source, enterprise-grade Identity & Access Management system. Features include:

  • OpenID Connect and OAuth2 compliance
  • Realm, client, and role management
  • JWKS endpoint for public key distribution
  • Seamless integration with Java via Spring Security

Alternatively, you could use Auth0, AWS Cognito, or any OAuth2 provider. But Keycloak is ideal for local development and open-source stacks.

System Architecture

Below is a simplified architecture diagram of the system:

graph TD
Client[Client Service] -->|client_credentials| Keycloak[Keycloak Auth Server]
Client -->|Bearer token| ApiServer[Secure REST API]
ApiServer -->|Validate JWT| Keycloak[Keycloak Auth Server]

Core Security Implementation

Securing the REST API involves configuring Spring Security to:

  • Accept and validate JWTs issued by Keycloak
  • Extract role claims from the token payload
  • Map those roles to Spring Security’s authorization model
  • Enforce fine-grained access control on HTTP endpoints

This is done in a modular and extensible way using a SecurityConfig class.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(AbstractHttpConfigurer::disable)
        .cors(AbstractHttpConfigurer::disable)
        .httpBasic(AbstractHttpConfigurer::disable)
        .formLogin(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/**").hasRole("READER")
        )
        .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt.jwtAuthenticationConverter(new KeycloakJwtAuthenticationConverter())));
    return http.build();
  }
}

And the configuration:

spring:
  application:
    name: secure-rest-api-server
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8081/realms/demo
          jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs

Let’s break down what’s happening:

  • @EnableMethodSecurity enables method-level annotations like @PreAuthorize.
  • We configure the app as an OAuth2 Resource Server, which means it expects JWTs from an Authorization Server (Keycloak).
  • JWTs are verified against the Keycloak JWKS endpoint.
  • A custom converter is used to extract realm_access.roles from the token payload and translate them into Spring GrantedAuthoritys. This is crucial, because Keycloak’s default token structure doesn’t align out-of-the-box with Spring’s expectations.

Service-to-Service Client Implementation

The client is a standalone Spring Boot app that simulates a non-human service actor — a common case in microservice architectures. It uses the OAuth2 Client Credentials Grant to obtain a token and then calls the secured API.

At a high level, it does three things:

  1. Requests a token from Keycloak using a client_id and client_secret
  2. Extracts the access_token from the response
  3. Uses that token to call the /products endpoint of the secured API

Simplified Token Service:

public class TokenService {
    public String fetchAccessToken() {
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "client_credentials");
        body.add("client_id", "secure-rest-api");
        body.add("client_secret", "secure-api-secret");

        Map<String, Object> tokenResponse = RestClient.create().post()
            .uri("http://localhost:8081/realms/demo/protocol/openid-connect/token")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .body(body)
            .retrieve()
            .body(Map.class);

        return tokenResponse.get("access_token").toString();
    }
}

And simplified Client Service class that uses the token:

public class ApiClientService {
    public void callSecureApi(String token) {
        String response = RestClient.create().get()
            .uri("http://localhost:8080/products")
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
            .retrieve()
            .body(String.class);

        System.out.println("Response: " + response);
    }
}

From an architectural perspective, this mirrors what real services would do in production — authenticate with the auth server, obtain a JWT, and use it as a bearer token in HTTP requests. This avoids managing static API keys or user sessions.

Conclusion

Securing backend services via OAuth2 and Keycloak isn’t just about authentication—it’s about designing for Zero Trust, resilience, and enterprise readiness. This system is small, modular, and easy to understand, yet powerful through role-based access, stateless tokens, and tested flows.

Explore the code on GitHub, star the project, and stay tuned for future modules demonstrating cloud-native patterns and enterprise pipelines.

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top