Security Top

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

>> CHECK OUT THE COURSE

1. Overview

Keycloak is an open-source Identity and Access Management solution administered by RedHat, and developed in Java by JBoss.

In this tutorial, we'll learn how to set up a Keycloak server embedded in a Spring Boot application. This makes it easy to start-up a pre-configured Keycloak server.

Keycloak can also be run as a standalone server, but then it involves downloading it and setup via the Admin Console.

2. Keycloak Pre-Configuration

To start with, let's understand how we can pre-configure a Keycloak server.

The server contains a set of realms, with each realm acting as an isolated unit for user management. To pre-configure it, we need to specify a realm definition file in a JSON format.

Everything that can be configured using the Keycloak Admin Console is persisted in this JSON. 

Our Authorization Server will be pre-configured with baeldung-realm.json. Let's see a few relevant configurations in the file:

  • users: our default users would be [email protected] and [email protected]; they'll also have their credentials here
  • clients: we'll define a client with the id newClient
  • standardFlowEnabled:  set to true to activate Authorization Code Flow for newClient
  • redirectUris: newClient‘s URLs that the server will redirect to after successful authentication are listed here
  • webOrigins: set to “+” to allow CORS support for all URLs listed as redirectUris

The Keycloak server issues JWT tokens by default, so there is no separate configuration required for that. Let's look at the Maven configurations next.

3. Maven Configuration

Since we'll embed Keycloak inside of a Spring Boot application, there is no need to download it separately.

Instead, we'll set up the following set of dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>        
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
       

Note that we're using Spring Boot's 2.2.6.RELEASE version here. The dependencies spring-boot-starter-data-jpa and H2 have been added for persistence. The other springframework.boot dependencies are for web support, as we also need to be able to run the Keycloak authorization server as well as admin console as web services.

We'll also need a couple of dependencies for Keycloak and RESTEasy:

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson2-provider</artifactId>
    <version>3.11.2.Final</version>
</dependency>

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-dependencies-server-all</artifactId>
    <version>10.0.1</version>
    <type>pom</type>
</dependency> 

Check the Maven site for latest versions of Keycloak and RESTEasy.

4.  Embedded Keycloak Configuration

Now let's define the Spring configuration for our authorization server:

@Configuration
public class EmbeddedKeycloakConfig {

    @Bean
    ServletRegistrationBean keycloakJaxRsApplication(
      KeycloakServerProperties keycloakServerProperties, DataSource dataSource) throws Exception {
        
        mockJndiEnvironment(dataSource);
        EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties;
        ServletRegistrationBean servlet = new ServletRegistrationBean<>(
          new HttpServlet30Dispatcher());
        servlet.addInitParameter("javax.ws.rs.Application", 
          EmbeddedKeycloakApplication.class.getName());
        servlet.addInitParameter(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX,
          keycloakServerProperties.getContextPath());
        servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, 
          "true");
        servlet.addUrlMappings(keycloakServerProperties.getContextPath() + "/*");
        servlet.setLoadOnStartup(1);
        servlet.setAsyncSupported(true);
        return servlet;
    }

    @Bean
    FilterRegistrationBean keycloakSessionManagement(
      KeycloakServerProperties keycloakServerProperties) { 
        FilterRegistrationBean filter = new FilterRegistrationBean<>();
        filter.setName("Keycloak Session Management");
        filter.setFilter(new KeycloakSessionServletFilter());
        filter.addUrlPatterns(keycloakServerProperties.getContextPath() + "/*");
        return filter;
    }

    private void mockJndiEnvironment(DataSource dataSource) throws NamingException {		 
        NamingManager.setInitialContextFactoryBuilder(
          (env) -> (environment) -> new InitialContext() {
            @Override
            public Object lookup(Name name) {
                return lookup(name.toString());
            }
	
            @Override
            public Object lookup(String name) {
                if ("spring/datasource".equals(name)) {
                    return dataSource;
                }
                return null;
            }

            @Override
            public NameParser getNameParser(String name) {
                return CompositeName::new;
            }

            @Override
            public void close() {
            }
        });
    }
}

As we can see here, we first configured Keycloak as a JAX-RS application with KeycloakServerProperties for persistent storage of Keycloak properties as specified in our realm definition file. We then added a session management filter and mocked a JNDI environment to use a spring/datasource, which is our in-memory H2 database.

5. KeycloakServerProperties

Now let's have a look at the KeycloakServerProperties we just mentioned:

@ConfigurationProperties(prefix = "keycloak.server")
public class KeycloakServerProperties {
    String contextPath = "/auth";
    String realmImportFile = "baeldung-realm.json";
    AdminUser adminUser = new AdminUser();

    // getters and setters

    public static class AdminUser {
        String username = "admin";
        String password = "admin";

        // getters and setters        
    }
}

As we can see, this is a simple POJO to set the contextPath, adminUser and realm definition file.

6. EmbeddedKeycloakApplication

Next, let's see the class, which uses the configurations we set before, to create realms:

public class EmbeddedKeycloakApplication extends KeycloakApplication {
    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedKeycloakApplication.class);
    static KeycloakServerProperties keycloakServerProperties;

    protected void loadConfig() {
        JsonConfigProviderFactory factory = new RegularJsonConfigProviderFactory();
        Config.init(factory.create()
          .orElseThrow(() -> new NoSuchElementException("No value present")));
    }
    public EmbeddedKeycloakApplication() {
        createMasterRealmAdminUser();
        createBaeldungRealm();
    }

    private void createMasterRealmAdminUser() {
        KeycloakSession session = getSessionFactory().create();
        ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
        AdminUser admin = keycloakServerProperties.getAdminUser();
        try {
            session.getTransactionManager().begin();
            applianceBootstrap.createMasterRealmUser(admin.getUsername(), admin.getPassword());
            session.getTransactionManager().commit();
        } catch (Exception ex) {
            LOG.warn("Couldn't create keycloak master admin user: {}", ex.getMessage());
            session.getTransactionManager().rollback();
        }
        session.close();
    }

    private void createBaeldungRealm() {
        KeycloakSession session = getSessionFactory().create();
        try {
            session.getTransactionManager().begin();
            RealmManager manager = new RealmManager(session);
            Resource lessonRealmImportFile = new ClassPathResource(
              keycloakServerProperties.getRealmImportFile());
            manager.importRealm(JsonSerialization.readValue(lessonRealmImportFile.getInputStream(),
              RealmRepresentation.class));
            session.getTransactionManager().commit();
        } catch (Exception ex) {
            LOG.warn("Failed to import Realm json file: {}", ex.getMessage());
            session.getTransactionManager().rollback();
        }
        session.close();
    }
}

Here we first loaded Keycloak's server configuration keycloak-server.json, using an empty subclass of the abstract JsonConfigProviderFactory:

public class RegularJsonConfigProviderFactory extends JsonConfigProviderFactory { }

Then, we extended KeycloakApplication to create two realms: master and baeldung. These are created as per the properties specified in our realm definition file, baeldung-realm.json.

Additionally, we need a couple of custom providers so that we have our own implementations of org.keycloak.common.util.ResteasyProvider and org.keycloak.platform.PlatformProvider and do not rely on external dependencies.

Importantly, information about these custom providers should be included in the project's META-INF/services folder so that they are picked up at runtime.

7. Bringing It All Together

As we saw, Keycloak has much simplified the required configurations from the application side. There is no need to programmatically define the datasource or any security configurations.

To bring it all together, we need to define the configuration for Spring and a Spring Boot Application.

7.1. application.yml

We'll be using a simple YAML for the Spring configurations:

server:
  port: 8083

spring:
  datasource:
    username: sa
    url: jdbc:h2:mem:testdb

keycloak:
  server:
    contextPath: /auth
    adminUser:
      username: bael-admin
      password: ********
    realmImportFile: baeldung-realm.json

7.2. Spring Boot Application

Lastly, here's the Spring Boot Application:

@SpringBootApplication(exclude = LiquibaseAutoConfiguration.class)
@EnableConfigurationProperties(KeycloakServerProperties.class)
public class AuthorizationServerApp {
    private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerApp.class);
    
    public static void main(String[] args) throws Exception {
        SpringApplication.run(AuthorizationServerApp.class, args);
    }

    @Bean
    ApplicationListener<ApplicationReadyEvent> onApplicationReadyEventListener(
      ServerProperties serverProperties, KeycloakServerProperties keycloakServerProperties) {
        return (evt) -> {
            Integer port = serverProperties.getPort();
            String keycloakContextPath = keycloakServerProperties.getContextPath();
            LOG.info("Embedded Keycloak started: http://localhost:{}{} to use keycloak", 
              port, keycloakContextPath);
        };
    }
}

Notably, here we have enabled the KeycloakServerProperties configuration to inject it into the ApplicationListener bean.

After running this class, we can access the authorization server's welcome page at http://localhost:8083/auth/.

8. Conclusion

In this quick tutorial, we saw how to setup a Keycloak server embedded in a Spring Boot application. The source code for this application is available over on GitHub.

The original idea for this implementation was developed by Thomas Darimont and can be found in the project embedded-spring-boot-keycloak-server.

Security bottom

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

>> CHECK OUT THE COURSE
14 Comments
Oldest
Newest
Inline Feedbacks
View all comments
hurelhuyag
hurelhuyag
7 months ago

freemarker 8.0.0? 2.3.30?

Loredana Crusoveanu
7 months ago
Reply to  hurelhuyag

Thanks. The dependency is managed by Boot in this case, so I’ve removed the version.

Sebastian
Sebastian
7 months ago

When I try this example I get this error from liquibase:

Caused by: liquibase.exception.DatabaseException: Table “FED_CREDENTIAL_ATTRIBUTE ” not found; SQL statement:
DROP TABLE PUBLIC.”FED_CREDENTIAL_ATTRIBUTE ” [42102-200] [Failed SQL: (42102) DROP TABLE PUBLIC.”FED_CREDENTIAL_ATTRIBUTE “]

I assume that the script ‘jpa-changelog-8.0.0.xml’ has an extra space on line 201.

How do you managed to avoid this?

Loredana Crusoveanu
6 months ago
Reply to  Sebastian

Hi Sebastian,

The Authorization Server runs okay on port 8083 with the given code. If you upgrade the Boot version to 2.2.3 or above, you’ll indeed see this error, which is a bug reported to Keycloak: https://issues.redhat.com/browse/KEYCLOAK-13249 It’s marked as fixed in version 9.0.1 so you can try upgrading the Keycloak libraries to that version, though there may be other changes as well.

Alternatively, you can keep the 2.2.2 Boot version for the application.

Cheers.

Holger Stolzenberg
7 months ago

For a more easy integration with Spring Boot, you also may have a look at https://github.com/testcontainers/testcontainers-spring-boot#embedded-keycloak.

Loredana Crusoveanu
6 months ago

Thanks, Holger,

I wasn’t aware of that library. We’ll have a look.

Cheers.

Alexandru Tanasescu
Alexandru Tanasescu
6 months ago

The exclusions and exclusion tags are not correctly closed in the example

Loredana Crusoveanu
6 months ago

Thanks Alex, fixed.

Billy Gunawan
Billy Gunawan
6 months ago

I tried this tutorial and have encountered problem when i try mvn install so obviously i cant run the app. This the stacktrace and something i do different is i use SQL Server Database if it affects. I cant seem to find ScheduledTask in Keycloak package Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘keycloakJaxRsApplication’ defined in class path resource [com/ifabula/keycloak/configuration/EmbeddedKeycloakConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method ‘keycloakJaxRsApplication’ threw exception; nested exception is java.lang.NoClassDefFoundError: org/keycloak/timer/ScheduledTask Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method ‘keycloakJaxRsApplication’ threw exception; nested exception is… Read more »

Loredana Crusoveanu
6 months ago
Reply to  Billy Gunawan

Hi Bill,

I’m not able to reproduce the issue on our code here: https://github.com/Baeldung/spring-security-oauth/tree/master/oauth-rest/oauth-authorization-server I suggest trying to run it as it is, and if it’s working ok, then there may be some missing library related to SQL Server. Another possible cause is if you’re running this with a different Java version (the github code uses Java 13).

Unfortunately, we can only help with questions that are specifically and directly related to the article – not with your own, custom application.
StackOverflow is a great place to ask more general questions.

Cheers.

Comments are closed on this article!