Search

Chapter 6. Migrating custom providers

download PDF

Similarly to the Red Hat Single Sign-On 7.6, custom providers are deployed to the Red Hat build of Keycloak by copying them to a deployment directory. In the Red Hat build of Keycloak, copy your providers to the providers directory instead of standalone/deployments, which no longer exists. Additional dependencies should also be copied to the providers directory.

Red Hat build of Keycloak does not use a separate classpath for custom providers, so you may need to be more careful with additional dependencies that you include. In addition, the EAR and WAR packaging formats, and jboss-deployment-structure.xml files, are no longer supported.

While Red Hat Single Sign-On 7.6 automatically discovered custom providers, and even supported the ability to hot-deploy custom providers while Keycloak is running, this behavior is no longer supported. Also, after you make a change to the providers or dependencies in the providers directory, you have to do a build or restart the server with the auto build feature.

Depending on what APIs your providers use you may also need to make some changes to the providers. See the following sections for details.

6.1. Transition from Java EE to Jakarta EE

Keycloak migrated its codebase from Java EE (Enterprise Edition) to Jakarta EE, which brought various changes. We have upgraded all Jakarta EE specifications in order to support Jakarta EE 10, such as:

  • Jakarta Persistence 3.1
  • Jakarta RESTful Web Services 3.1
  • Jakarta Mail API 2.1
  • Jakarta Servlet 6.0
  • Jakarta Activation 2.1

Jakarta EE 10 provides a modernized, simplified, lightweight approach to building cloud-native Java applications. The main changes provided within this initiative are changing the namespace from javax.* to jakarta.*. This change does not apply for javax.* packages provided directly in the JDK, such as javax.security, javax.net, javax.crypto, etc.

In addition, Jakarta EE APIs like session/stateless beans are no longer supported.

6.2. Removed third party dependencies

Some dependencies were removed in Red Hat build of Keycloak including

  • openshift-rest-client
  • okio-jvm
  • okhttp
  • commons-lang
  • commons-compress
  • jboss-dmr
  • kotlin-stdlib

Also, since Red Hat build of Keycloak is no longer based on EAP, most of the EAP dependencies were removed. This change means that if you use any of these libraries as dependencies of your own providers deployed to the Red Hat build of Keycloak, you may also need to copy those JAR files explicitly to the Keycloak distribution providers directory.

6.3. Context and dependency injection are no longer enabled for JAX-RS Resources

To provide a better runtime and leverage as much as possible the underlying stack, all injection points for contextual data using the javax.ws.rs.core.Context annotation were removed. The expected improvement in performance involves no longer creating proxies instances multiple times during the request lifecycle, and drastically reducing the amount of reflection code at runtime.

If you need access to the current request and response objects, you can now obtain their instances directly from the KeycloakSession:

@Context
org.jboss.resteasy.spi.HttpRequest request;
@Context
org.jboss.resteasy.spi.HttpResponse response;

was replaced by:

KeycloakSession session = // obtain the session, which is usually available when creating a custom provider from a factory
KeycloakContext context = session.getContext();

HttpRequest request = context.getHttpRequest();
HttpResponse response = context.getHttpResponse();

Additional contextual data can be obtained from the runtime through the KeycloakContext instance:

KeycloakSession session = // obtain the session
KeycloakContext context = session.getContext();
MyContextualObject myContextualObject = context.getContextObject(MyContextualObject.class);

6.4. Deprecated methods from data providers and models

Some previously deprecated methods are now removed in Red Hat build of Keycloak:

  • RealmModel#searchForGroupByNameStream(String, Integer, Integer)
  • UserProvider#getUsersStream(RealmModel, boolean)
  • UserSessionPersisterProvider#loadUserSessions(int, int, boolean, int, String)
  • Interfaces added for Streamification work, such as RoleMapperModel.Streams and similar
  • KeycloakModelUtils#getClientScopeMappings
  • Deprecated methods from KeycloakSession
  • UserQueryProvider#getUsersStream methods

Also, these other changes were made:

  • Some methods from UserSessionProvider were moved to UserLoginFailureProvider.
  • Streams interfaces in federated storage provider classes were deprecated.
  • Streamification - interfaces now contain only Stream-based methods.

    For example in GroupProvider interface

    @Deprecated
    List<GroupModel> getGroups(RealmModel realm);

    was replaced by

    Stream<GroupModel> getGroupsStream(RealmModel realm);
  • Consistent parameter ordering - methods now have strict parameter ordering where RealmModel is always the first parameter.

    For example in UserLookupProvider interface:

    @Deprecated
    UserModel getUserById(String id, RealmModel realm);

    was replaced by

    UserModel getUserById(RealmModel realm, String id)

6.4.1. List of changed interfaces

(o.k. stands for org.keycloak. package)

  • server-spi module

    • o.k.credential.CredentialInputUpdater
    • o.k.credential.UserCredentialStore
    • o.k.models.ClientProvider
    • o.k.models.ClientSessionContext
    • o.k.models.GroupModel
    • o.k.models.GroupProvider
    • o.k.models.KeyManager
    • o.k.models.KeycloakSessionFactory
    • o.k.models.ProtocolMapperContainerModel
    • o.k.models.RealmModel
    • o.k.models.RealmProvider
    • o.k.models.RoleContainerModel
    • o.k.models.RoleMapperModel
    • o.k.models.RoleModel
    • o.k.models.RoleProvider
    • o.k.models.ScopeContainerModel
    • o.k.models.UserCredentialManager
    • o.k.models.UserModel
    • o.k.models.UserProvider
    • o.k.models.UserSessionProvider
    • o.k.models.utils.RoleUtils
    • o.k.sessions.AuthenticationSessionProvider
    • o.k.storage.client.ClientLookupProvider
    • o.k.storage.group.GroupLookupProvider
    • o.k.storage.user.UserLookupProvider
    • o.k.storage.user.UserQueryProvider
  • server-spi-private module

    • o.k.events.EventQuery
    • o.k.events.admin.AdminEventQuery
    • o.k.keys.KeyProvider

6.4.2. Refactorings in the storage layer

Red Hat build of Keycloak undergoes a large refactoring to simplify the API usage, which impacts existing code. Some of these changes require updates to existing code. The following sections provide more detail.

6.4.2.1. Changes in the module structure

Several public APIs around storage functionality in KeycloakSession have been consolidated, and some have been moved, deprecated, or removed. Three new modules have been introduced, and data-oriented code from server-spi, server-spi-private, and services modules have been moved there:

org.keycloak:keycloak-model-legacy
Contains all public facing APIs from the legacy store, such as the User Storage API.
org.keycloak:keycloak-model-legacy-private
Contains private implementations that relate to user storage management, such as storage *Manager classes.
org.keycloak:keycloak-model-legacy-services
Contains all REST endpoints that directly operate on the legacy store.

If you are using for example in your custom user storage provider implementation the classes which have been moved to the new modules, you need to update your dependencies to include the new modules listed above.

6.4.2.2. Changes in KeycloakSession

KeycloakSession has been simplified. Several methods have been removed in KeycloakSession.

KeycloakSession session contained several methods for obtaining a provider for a particular object type, such as for a UserProvider there are users(), userLocalStorage(), userCache(), userStorageManager(), and userFederatedStorage(). This situation may be confusing for the developer who has to understand the exact meaning of each method.

For those reasons, only the users() method is kept in KeycloakSession, and should replace all other calls listed above. The rest of the methods have been removed. The same pattern of depreciation applies to methods of other object areas, such as clients() or groups(). All methods ending in *StorageManager() and *LocalStorage() have been removed. The next section describes how to migrate those calls to the new API or use the legacy API.

6.4.3. Migrating existing providers

The existing providers need no migration if they do not call a removed method, which should be the case for most providers.

If the provider uses removed methods, but does not rely on local versus non-local storage, changing a call from the now removed userLocalStorage() to the method users() is the best option. Be aware that the semantics change here as the new method involves a cache if that has been enabled in the local setup.

Before migration: accessing a removed API doesn’t compile

session.userLocalStorage();

After migration: accessing the new API when caller does not depend on the legacy storage API

session.users();

In the rare case when a custom provider needs to distinguish between the mode of a particular provider, access to the deprecated objects is provided by using the LegacyStoreManagers data store provider. This might be the case if the provider accesses the local storage directly or wants to skip the cache. This option will be available only if the legacy modules are part of the deployment.

Before migration: accessing a removed API

session.userLocalStorage();

After migration: accessing the new functionality via the LegacyStoreManagers API

((LegacyDatastoreProvider) session.getProvider(DatastoreProvider.class)).userLocalStorage();

Some user storage related APIs have been wrapped in org.keycloak.storage.UserStorageUtil for convenience.

6.4.4. Changes to RealmModel

The methods getUserStorageProviders, getUserStorageProvidersStream, getClientStorageProviders, getClientStorageProvidersStream, getRoleStorageProviders and getRoleStorageProvidersStream have been removed. Code which depends on these methods should cast the instance as follows:

Before migration: code will not compile due to the changed API

realm.getClientStorageProvidersStream()...;

After migration: cast the instance to the legacy interface

((LegacyRealmModel) realm).getClientStorageProvidersStream()...;

Similarly, code that used to implement the interface RealmModel and wants to provide these methods should implement the new interface LegacyRealmModel. This interface is a sub-interface of RealmModel and includes the old methods:

Before migration: code implements the old interface

public class MyClass extends RealmModel {
    /* might not compile due to @Override annotations for methods no longer present
       in the interface RealmModel. / / ... */
}

After migration: code implements the new interface

public class MyClass extends LegacyRealmModel {
    /* ... */
}

6.4.5. Interface UserCache moved to the legacy module

As the caching status of objects will be transparent to services, the interface UserCache has been moved to the module keycloak-model-legacy.

Code that depends on the legacy implementation should access the UserCache directly.

Before migration: code will not compile[source,java,subs="+quotes"]

session**.userCache()**.evict(realm, user);

After migration: use the API directly

UserStorageUitl.userCache(session);

To trigger the invalidation of a realm, instead of using the UserCache API, consider triggering an event:

Before migration: code uses cache API[source,java,subs="+quotes"]

UserCache cache = session.getProvider(UserCache.class);
if (cache != null) cache.evict(realm)();

After migration: use the invalidation API

session.invalidate(InvalidationHandler.ObjectType.REALM, realm.getId());

6.4.6. Credential management for users

Credentials for users were previously managed using session.userCredentialManager().method(realm, user, ...). The new way is to leverage user.credentialManager().method(...). This form gets the credential functionality closer to the API of users, and does not rely on prior knowledge of the user credential’s location in regard to realm and storage.

The old APIs have been removed.

Before migration: accessing a removed API

session.userCredentialManager().createCredential(realm, user, credentialModel)

After migration: accessing the new API

user.credentialManager().createStoredCredential(credentialModel)

For a custom UserStorageProvider, there is a new method credentialManager() that needs to be implemented when returning a UserModel. Those must return an instance of the LegacyUserCredentialManager:

Before migration: code will not compile due to the new method credentialManager() required by UserModel

public class MyUserStorageProvider implements UserLookupProvider, ... {
    /* ... */
    protected UserModel createAdapter(RealmModel realm, String username) {
        return new AbstractUserAdapter(session, realm, model) {
            @Override
            public String getUsername() {
                return username;
            }
        };
    }
}

After migration: implementation of the API UserModel.credentialManager() for the legacy store.

public class MyUserStorageProvider implements UserLookupProvider, ... {
    /* ... */
    protected UserModel createAdapter(RealmModel realm, String username) {
        return new AbstractUserAdapter(session, realm, model) {
            @Override
            public String getUsername() {
                return username;
            }

            @Override
            public SubjectCredentialManager credentialManager() {
                return new LegacyUserCredentialManager(session, realm, this);
            }
        };
    }
}

Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.