Extending the Server

The Keycloak SPI framework offers the possibility to implement or override particular built-in providers. However Keycloak also provides capabilities to extend it’s core functionalities and domain. This includes possibilities to:

  • Add custom REST endpoints to the Keycloak server

  • Add your own custom SPI

  • Add custom JPA entities to the Keycloak data model

Add custom REST endpoints

This is a very powerful extension, which allows you to deploy your own REST endpoints to the Keycloak server. It enables all kinds of extensions, for example the possibility to trigger functionality on the Keycloak server, which is not available through the default set of built-in Keycloak REST endpoints.

To add a custom REST endpoint, you need to implement the RealmResourceProviderFactory and RealmResourceProvider interfaces. RealmResourceProvider has one important method:

Object getResource();

which allows you to return an object, which acts as a JAX-RS Resource. For more details, see the Javadoc and our examples. There is a very simple example in the example distribution in providers/rest and there is a more advanced example in providers/domain-extension, which shows how to add an authenticated REST endpoint and other functionalities like Adding your own SPI or Extending datamodel with your own JPA entities.

For details on how to package and deploy a custom provider, refer to the Service Provider Interfaces chapter.

Add your own custom SPI

This is useful especially with the Custom REST endpoints. To add your own kind of SPI, you need to implement the interface org.keycloak.provider.Spi and define the ID of your SPI and the ProviderFactory and Provider classes. That looks like this:

package org.keycloak.examples.domainextension.spi;

import ...

public class ExampleSpi implements Spi {

    @Override
    public boolean isInternal() {
        return false;
    }

    @Override
    public String getName() {
        return "example";
    }

    @Override
    public Class<? extends Provider> getProviderClass() {
        return ExampleService.class;
    }

    @Override
    @SuppressWarnings("rawtypes")
    public Class<? extends ProviderFactory> getProviderFactoryClass() {
        return ExampleServiceProviderFactory.class;
    }

}

Then you need to create the file META-INF/services/org.keycloak.provider.Spi and add the class of your SPI to it. For example:

org.keycloak.examples.domainextension.spi.ExampleSpi

The next step is to create the interfaces ExampleServiceProviderFactory, which extends from ProviderFactory and ExampleService, which extends from Provider. The ExampleService will usually contain the business methods you need for your use case. Note that the ExampleServiceProviderFactory instance is always scoped per application, however ExampleService is scoped per-request (or more accurately per KeycloakSession lifecycle).

Finally you need to implement your providers in the same manner as described in the Service Provider Interfaces chapter.

For more details, take a look at the example distribution at providers/domain-extension, which shows an Example SPI similar to the one above.

Add custom JPA entities to the Keycloak data model

If the Keycloak data model does not exactly match your desired solution, or if you want to add some core functionality to Keycloak, or when you have your own REST endpoint, you might want to extend the Keycloak data model. We enable you to add your own JPA entities to the Keycloak JPA EntityManager .

To add your own JPA entities, you need to implement JpaEntityProviderFactory and JpaEntityProvider. The JpaEntityProvider allows you to return a list of your custom JPA entities and provide the location and id of the liquibase changelog. An example implementation can look like this:

public class ExampleJpaEntityProvider implements JpaEntityProvider {

    // List of your JPA entities.
    @Override
    public List<Class<?>> getEntities() {
        return Collections.<Class<?>>singletonList(Company.class);
    }

    // This is used to return the location of the Liquibase changelog file.
    // You can return null if you don't want Liquibase to create and update the DB schema.
    @Override
    public String getChangelogLocation() {
    	return "META-INF/example-changelog.xml";
    }

    // Helper method, which will be used internally by Liquibase.
    @Override
    public String getFactoryId() {
        return "sample";
    }

    ...
}

In the example above, we added a single JPA entity represented by class Company. In the code of your REST endpoint, you can then use something like this to retrieve EntityManager and call DB operations on it.

EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
Company myCompany = em.find(Company.class, "123");

The methods getChangelogLocation and getFactoryId are important to support automatic updating of your entities by Liquibase. Liquibase is a framework for updating the database schema, which Keycloak internally uses to create the DB schema and update the DB schema among versions. You may need to use it as well and create a changelog for your entities. Note that versioning of your own liquibase changelog is independent of Keycloak versions. In other words, when you update to a new Keycloak version, you are not forced to update your schema at the same time. And vice versa, you can update your schema even without updating the Keycloak version. The Liquibase update is always done at the server startup, so to trigger a DB update of your schema, you just need to add the new changeset to your liquibase changelog file (in the example above it’s the file META-INF/example-changelog.xml (which must be packed in same JAR as the JPA entities and ExampleJpaEntityProvider) and then restart server. The DB schema will be automatically updated at startup.

For more details, take a look at the example distribution at example providers/domain-extension, which shows the ExampleJpaEntityProvider and example-changelog.xml described above.

Note
Don’t forget to always backup your database before doing any changes in the Liquibase changelog and triggering a DB update.