Chapter 6. Migrating custom providers 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 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"] 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"] 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); } }; } }

Commands:
@Context org.jboss.resteasy.spi.HttpRequest request; @Context org.jboss.resteasy.spi.HttpResponse response;
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();
KeycloakSession session = // obtain the session KeycloakContext context = session.getContext(); MyContextualObject myContextualObject = context.getContextObject(MyContextualObject.class);
@Deprecated List<GroupModel> getGroups(RealmModel realm);
Stream<GroupModel> getGroupsStream(RealmModel realm);
@Deprecated UserModel getUserById(String id, RealmModel realm);
UserModel getUserById(RealmModel realm, String id)
session .userLocalStorage() ;
session .users() ;
session .userLocalStorage() ;
((LegacyDatastoreProvider) session.getProvider(DatastoreProvider.class)) .userLocalStorage() ;
realm .getClientStorageProvidersStream() ...;
((LegacyRealmModel) realm) .getClientStorageProvidersStream() ...;
public class MyClass extends RealmModel { /* might not compile due to @Override annotations for methods no longer present in the interface RealmModel. / / ... */ }
public class MyClass extends LegacyRealmModel { /* ... */ }
session**.userCache()**.evict(realm, user);
UserStorageUitl.userCache(session);
UserCache cache = session.getProvider(UserCache.class); if (cache != null) cache.evict(realm)();
session.invalidate(InvalidationHandler.ObjectType.REALM, realm.getId());
session.userCredentialManager() .createCredential (realm, user, credentialModel)
user.credentialManager() .createStoredCredential (credentialModel)
public class MyUserStorageProvider implements UserLookupProvider, ... { /* ... */ protected UserModel createAdapter(RealmModel realm, String username) { return new AbstractUserAdapter(session, realm, model) { @Override public String getUsername() { return username; } }; } }
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); } }; } }