Secure Spring Boot Microservices with Keycloak

In this tutorial, we will secure a Spring Boot microservices architecture using Keycloak. We will cover setting up Keycloak, configuring Spring Boot microservices to use Keycloak for authentication and authorization, and running the services.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • Docker installed
  • Docker Compose installed
  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Overview of the Microservices

We will create three Spring Boot microservices:

  1. Employee Service: Manages employee information.
  2. Department Service: Manages department information.
  3. API Gateway: Routes requests and handles security with Keycloak.

We will also set up Keycloak as our identity provider.

Step 1: Setting Up Keycloak

1.1 Run Keycloak with Docker

Create a docker-compose.yml file to run Keycloak:

version: '3'

services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    command: start-dev
    ports:
      - "8080:8080"

Run the following command to start Keycloak:

docker-compose up -d

1.2 Configure Keycloak

  1. Access Keycloak Admin Console:

    • Open your web browser and go to http://localhost:8080.
    • Log in with the username admin and password admin.
  2. Create a Realm:

    • Click on the Master drop-down on the top left corner and select Create Realm.
    • Name the realm springboot.
  3. Create a Client:

    • In the springboot realm, go to Clients and click Create.
    • Name the client springboot-client and set the client type to OpenID Connect.
    • Set the Root URL to http://localhost:8080 and click Save.
    • In the Settings tab, set Access Type to confidential and set Service Accounts Enabled to ON.
    • Copy the Client Secret from the Credentials tab.
  4. Create a Role:

    • Go to Roles and click Add Role.
    • Name the role user and click Save.
  5. Create a User:

    • Go to Users and click Add User.
    • Set the username to testuser and click Save.
    • In the Credentials tab, set a password and turn off Temporary.
    • In the Role Mappings tab, assign the user role to the user.

Step 2: Create the Employee Service

2.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: employee-service
    • Name: employee-service
    • Description: Employee Service
    • Package Name: com.example.employeeservice
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Web
      • Spring Data JPA
      • Spring Security
      • Keycloak Spring Boot Starter
      • H2 Database
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

2.2 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

server.port=8081

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

keycloak.realm=springboot
keycloak.auth-server-url=http://localhost:8080
keycloak.resource=springboot-client
keycloak.credentials.secret=YOUR_CLIENT_SECRET
keycloak.bearer-only=true
keycloak.security-constraints[0].authRoles[0]=user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/employees/*

Replace YOUR_CLIENT_SECRET with the client secret you copied from the Keycloak admin console.

2.3 Create Employee Entity

Create an Employee entity class in the com.example.employeeservice.model package:

package com.example.employeeservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    // Getters and Setters
}

2.4 Create Employee Repository

Create an EmployeeRepository interface in the com.example.employeeservice.repository package:

package com.example.employeeservice.repository;

import com.example.employeeservice.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

2.5 Create Employee Controller

Create an EmployeeController class in the com.example.employeeservice.controller package:

package com.example.employeeservice.controller;

import com.example.employeeservice.model.Employee;
import com.example.employeeservice.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeRepository employeeRepository;

    @Autowired
    public EmployeeController(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @GetMapping
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @PostMapping
    public Employee createEmployee(@RequestBody Employee employee) {
        return employeeRepository.save(employee);
    }
}

2.6 Create Security Configuration

Create a SecurityConfig class in the com.example.employeeservice.config package:

package com.example.employeeservice.config;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
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.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/employees/*").hasRole("user")
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        return http.build();
    }

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public SimpleAuthorityMapper grantedAuthorityMapper() {
        return new SimpleAuthorityMapper();
    }
}

Step 3: Create the Department Service

3.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: department-service
    • Name: department-service
    • Description: Department Service
    • Package Name: com.example.departmentservice
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Web
      • Spring Data JPA
      • Spring Security
      • Keycloak Spring Boot Starter
      • H2 Database
    • Click `Next

`.

  1. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  2. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

3.2 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

server.port=8082

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

keycloak.realm=springboot
keycloak.auth-server-url=http://localhost:8080
keycloak.resource=springboot-client
keycloak.credentials.secret=YOUR_CLIENT_SECRET
keycloak.bearer-only=true
keycloak.security-constraints[0].authRoles[0]=user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/departments/*

Replace YOUR_CLIENT_SECRET with the client secret you copied from the Keycloak admin console.

3.3 Create Department Entity

Create a Department entity class in the com.example.departmentservice.model package:

package com.example.departmentservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    // Getters and Setters
}

3.4 Create Department Repository

Create a DepartmentRepository interface in the com.example.departmentservice.repository package:

package com.example.departmentservice.repository;

import com.example.departmentservice.model.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}

3.5 Create Department Controller

Create a DepartmentController class in the com.example.departmentservice.controller package:

package com.example.departmentservice.controller;

import com.example.departmentservice.model.Department;
import com.example.departmentservice.repository.DepartmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/departments")
public class DepartmentController {

    private final DepartmentRepository departmentRepository;

    @Autowired
    public DepartmentController(DepartmentRepository departmentRepository) {
        this.departmentRepository = departmentRepository;
    }

    @GetMapping
    public List<Department> getAllDepartments() {
        return departmentRepository.findAll();
    }

    @PostMapping
    public Department createDepartment(@RequestBody Department department) {
        return departmentRepository.save(department);
    }
}

3.6 Create Security Configuration

Create a SecurityConfig class in the com.example.departmentservice.config package:

package com.example.departmentservice.config;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
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.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/departments/*").hasRole("user")
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        return http.build();
    }

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public SimpleAuthorityMapper grantedAuthorityMapper() {
        return new SimpleAuthorityMapper();
    }
}

Step 4: Create the API Gateway

4.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: api-gateway
    • Name: api-gateway
    • Description: API Gateway
    • Package Name: com.example.apigateway
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Cloud Gateway
      • Spring Security
      • Keycloak Spring Boot Starter
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

4.2 Update application.yml

Create an application.yml file in the src/main/resources directory and configure it as follows:

server:
  port: 8080

spring:
  application:
    name: api-gateway

  cloud:
    gateway:
      routes:
        - id: employee_service
          uri: http://localhost:8081
          predicates:
            - Path=/employees/**
        - id: department_service
          uri: http://localhost:8082
          predicates:
            - Path=/departments/**

keycloak:
  realm: springboot
  auth-server-url: http://localhost:8080
  resource: springboot-client
  credentials:
    secret: YOUR_CLIENT_SECRET
  bearer-only: true

Replace YOUR_CLIENT_SECRET with the client secret you copied from the Keycloak admin console.

4.3 Create Security Configuration

Create a SecurityConfig class in the com.example.apigateway.config package:

package com.example.apigateway.config;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
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.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/employees/*", "/departments/*").hasRole("user")
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        return http.build();
    }

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public SimpleAuthorityMapper grantedAuthorityMapper() {
        return new SimpleAuthorityMapper();
    }
}

Step 5: Running the Microservices

5.1 Running Authentication Service

  1. Open the AuthServiceApplication class in the src/main/java/com/example/authservice directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

5.2 Running Employee Service

  1. Open the EmployeeServiceApplication class in the src/main/java/com/example/employeeservice directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

5.3 Running Department Service

  1. Open the DepartmentServiceApplication class in the src/main/java/com/example/departmentservice directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

5.4 Running API Gateway

  1. Open the ApiGatewayApplication class in the src/main/java/com/example/apigateway directory.
  2. Click the green Run button in your IDE or use the terminal to run

the application:

./mvnw spring-boot:run

Step 6: Testing the Application

You can use tools like Postman to test the authentication and secure access to the microservices.

6.1 Get Access Token

  1. Create a new POST request to http://localhost:8080/realms/springboot/protocol/openid-connect/token.
  2. In the Body tab, select x-www-form-urlencoded and add the following parameters:
    • client_id: springboot-client
    • username: testuser
    • password: testpassword
    • grant_type: password
    • client_secret: YOUR_CLIENT_SECRET
  3. Send the request and copy the access token from the response.

6.2 Access Secured Employee Service

  1. Create a new GET request to http://localhost:8080/employees.
  2. Add the Authorization header with the value Bearer <ACCESS_TOKEN>.
  3. Send the request and verify that you receive the list of employees.

6.3 Access Secured Department Service

  1. Create a new GET request to http://localhost:8080/departments.
  2. Add the Authorization header with the value Bearer <ACCESS_TOKEN>.
  3. Send the request and verify that you receive the list of departments.

Conclusion

In this tutorial, we created a microservices architecture with Spring Boot and secured it using Keycloak. We built an authentication service to issue tokens, secured the employee and department services with Keycloak, and created an API Gateway to route and secure requests. This setup provides a solid foundation for developing secure microservices architectures.


Comments