Use OIDC and keycloak to protect your API

Table of Contents

1. Setup Keycloak

Before you start coding, make sure you have a running Keycloak instance:

Run Keycloak using Docker

docker run -d --name keycloak -p 8080:8080 \
    -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
    quay.io/keycloak/keycloak:latest start-dev

Create a Realm and Client

  1. Open Keycloak at http://localhost:8080/
  2. Login with admin/admin
  3. Create a new Realm (e.g., myrealm)
  4. Under the Realm, create a Client:
    • Client ID: myclient
    • Client Type: OpenID Connect
    • Root URL: http://localhost:8080
    • Click Save
  5. Under Client Authentication, set:
    • Access Type: confidential
    • Enable Service Accounts
    • Save the Client Secret (You'll use this in Spring Boot)
  6. Create a new User:
    • Go to Users > Create
    • Set username/password (testuser/testpassword)
    • Assign a role (user)

2. Spring Boot Project Setup

Add Dependencies (Maven)

Add the required dependencies for Keycloak and Spring Security:

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security with OAuth2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

    <!-- Spring Boot Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- Keycloak Spring Security Adapter -->
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-boot-starter</artifactId>
        <version>22.0.1</version>
    </dependency>
</dependencies>

3. Configure application.yml

server:
  port: 8081  # Run on a different port than Keycloak

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/myrealm
  application:
    name: my-secure-app

keycloak:
  realm: myrealm
  auth-server-url: http://localhost:8080
  resource: myclient
  credentials:
    secret: YOUR_CLIENT_SECRET
  bearer-only: true  # This means it will act as a Resource Server

4. Secure API Endpoints

Create a secured REST API with role-based access:

package com.example.demo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class SecureController {

    @GetMapping("/public")
    public String publicEndpoint() {
        return "This is a public endpoint";
    }

    @GetMapping("/user")
    @PreAuthorize("hasAuthority('ROLE_user')")
    public String userEndpoint() {
        return "Hello, User! You have access to this protected endpoint.";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasAuthority('ROLE_admin')")
    public String adminEndpoint() {
        return "Hello, Admin! You have elevated access.";
    }
}

5. Configure Security

Create a security config class:

package com.example.demo.config;

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.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public").permitAll()
                .requestMatchers("/api/user").hasRole("user")
                .requestMatchers("/api/admin").hasRole("admin")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); // Keycloak uses roles without "ROLE_" prefix
        grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

6. Test the Security

Public API (No Token Needed)

curl http://localhost:8081/api/public

Protected API (Requires Token)

  1. Get an access token from Keycloak:
    export TOKEN=$(curl -X POST 'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -d 'client_id=myclient' \
     -d 'client_secret=YOUR_CLIENT_SECRET' \
     -d 'username=testuser' \
     -d 'password=testpassword' \
     -d 'grant_type=password' | jq -r .access_token)
  2. Access the protected /user endpoint:
    curl -H "Authorization: Bearer $TOKEN" http://localhost:8081/api/user
  3. Try accessing the /admin endpoint:
    curl -H "Authorization: Bearer $TOKEN" http://localhost:8081/api/admin

    If your user doesn't have the admin role, it should return 403 Forbidden.


Conclusion

This setup integrates Spring Boot with Keycloak using OIDC to protect APIs:

  • Keycloak is the authentication provider.
  • Spring Security handles authentication via JWT.
  • Role-based access is configured via @PreAuthorize.

Would you like help setting up a frontend client for login? 🚀

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: 似水流年