-
Language:
English
-
Language:
English
Chapter 6. Service Provider Interfaces (SPI)
Red Hat Single Sign-On is designed to cover most use-cases without requiring custom code, but we also want it to be customizable. To achieve this Red Hat Single Sign-On has a number of Service Provider Interfaces (SPI) for which you can implement your own providers.
6.1. Implementing a SPI
To implement an SPI you need to implement its ProviderFactory and Provider interfaces. You also need to create a service configuration file.
For example, to implement the Theme Selector Spi you need to implement ThemeSelectorProviderFactory and ThemeSelectorProvider and also provide the file META-INF/services/org.keycloak.theme.ThemeSelectorProviderFactory
.
Example ThemeSelectorProviderFactory:
package org.acme.provider; import ... public class MyThemeSelectorProviderFactory implements ThemeSelectorProviderFactory { @Override public ThemeSelectorProvider create(KeycloakSession session) { return new MyThemeSelectorProvider(session); } @Override public void init(Config.Scope config) { } @Override public void postInit(KeycloakSessionFactory factory) { } @Override public void close() { } @Override public String getId() { return "myThemeSelector"; } }
Keycloak creates a single instance of provider factories which makes it possible to store state for multiple requests. Provider instances are created by calling create on the factory for each requests so these should be light-weight object.
Example ThemeSelectorProvider:
package org.acme.provider; import ... public class MyThemeSelectorProvider implements ThemeSelectorProvider { public MyThemeSelectorProvider(KeycloakSession session) { } @Override public String getThemeName(Theme.Type type) { return "my-theme"; } @Override public void close() { } }
Example service configuration file (META-INF/services/org.keycloak.theme.ThemeSelectorProviderFactory
):
org.acme.provider.MyThemeSelectorProviderFactory
You can configure your provider through standalone.xml
, standalone-ha.xml
, or domain.xml
. See the Server Installation and Configuration Guide for more details on where the standalone.xml
, standalone-ha.xml
, or domain.xml
file lives.
For example by adding the following to standalone.xml
:
<spi name="themeSelector"> <provider name="myThemeSelector" enabled="true"> <properties> <property name="theme" value="my-theme"/> </properties> </provider> </spi>
Then you can retrieve the config in the ProviderFactory
init method:
public void init(Config.Scope config) { String themeName = config.get("theme"); }
Your provider can also lookup other providers if needed. For example:
public class MyThemeSelectorProvider implements EventListenerProvider { private KeycloakSession session; public MyThemeSelectorProvider(KeycloakSession session) { this.session = session; } @Override public String getThemeName(Theme.Type type) { return session.getContext().getRealm().getLoginTheme(); } }
6.1.1. Show info from your SPI implementation in admin console
Sometimes it is useful to show additional info about your Provider to a Red Hat Single Sign-On administrator. You can show provider build time informations (eg. version of custom provider currently installed), current configuration of the provider (eg. url of remote system your provider talks to) or some operational info (average time of response from remote system your provider talks to). Red Hat Single Sign-On admin console provides Server Info page to show this kind of information.
To show info from your provider it is enough to implement org.keycloak.provider.ServerInfoAwareProviderFactory
interface in your ProviderFactory
.
Example implementation for MyThemeSelectorProviderFactory
from previous example:
package org.acme.provider; import ... public class MyThemeSelectorProvider implements ThemeSelectorProvider, ServerInfoAwareProviderFactory { ... @Override public Map<String, String> getOperationalInfo() { Map<String, String> ret = new LinkedHashMap<>(); ret.put("theme-name", "my-theme"); return ret; } }
6.2. Registering provider implementations
There are two ways to register provider implementations. In most cases the simplest way is to use the Red Hat Single Sign-On deployer approach as this handles a number of dependencies automatically for you. It also supports hot deployment as well as re-deployment.
The alternative approach is to deploy as a module.
If you are creating a custom SPI you will need to deploy it as a module, otherwise we recommend using the Red Hat Single Sign-On deployer approach.
6.2.1. Using the Red Hat Single Sign-On Deployer
If you copy your provider jar to the Red Hat Single Sign-On standalone/deployments/
directory, your provider will automatically be deployed. Hot deployment works too. Additionally, your provider jar works similarly to other components deployed in a JBoss EAP environment in that they can use facilities like the jboss-deployment-structure.xml
file. This file allows you to set up dependencies on other components and load third-party jars and modules.
Provider jars can also be contained within other deployable units like EARs and WARs. Deploying with a EAR actually makes it really easy to use third party jars as you can just put these libraries in the EAR’s lib/
directory.
6.2.2. Register a provider using Modules
To register a provider using Modules first create a module. To do this you can either use the jboss-cli script or manually create a folder inside KEYCLOAK_HOME/modules
and add your jar and a module.xml
. For example to add the event listener sysout example provider using the jboss-cli
script execute:
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.acme.provider --resources=target/provider.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
Or to manually create it start by creating the folder KEYCLOAK_HOME/modules/org/acme/provider/main
. Then copy provider.jar
to this folder and create module.xml
with the following content:
<?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.3" name="org.acme.provider"> <resources> <resource-root path="provider.jar"/> </resources> <dependencies> <module name="org.keycloak.keycloak-core"/> <module name="org.keycloak.keycloak-server-spi"/> </dependencies> </module>
Once you’ve created the module you need to register this module with Red Hat Single Sign-On. This is done by editing the keycloak-server subsystem section of standalone.xml
, standalone-ha.xml
, or domain.xml
, and adding it to the providers:
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1"> <web-context>auth</web-context> <providers> <provider>module:org.keycloak.examples.event-sysout</provider> </providers> ...
6.2.3. Disabling a provider
You can disable a provider by setting the enabled attribute for the provider to false in standalone.xml
, standalone-ha.xml
, or domain.xml
. For example to disable the Infinispan user cache provider add:
<spi name="userCache"> <provider name="infinispan" enabled="false"/> </spi>
6.3. Leveraging Java EE
The service providers can be packaged within any Java EE component so long as you set up the META-INF/services
file correctly to point to your providers. For example, if your provider needs to use third party libraries, you can package up your provider within an ear and store these third pary libraries in the ear’s lib/
directory. Also note that provider jars can make use of the jboss-deployment-structure.xml
file that EJBs, WARS, and EARs can use in a JBoss EAP environment. See the JBoss EAP documentation for more details on this file. It allows you to pull in external dependencies among other fine grain actions.
ProviderFactory
implementations are required to be plain java objects. But, we also currently support implementing provider classes as Stateful EJBs. This is how you would do it:
@Stateful @Local(EjbExampleUserStorageProvider.class) public class EjbExampleUserStorageProvider implements UserStorageProvider, UserLookupProvider, UserRegistrationProvider, UserQueryProvider, CredentialInputUpdater, CredentialInputValidator, OnUserCache { @PersistenceContext protected EntityManager em; protected ComponentModel model; protected KeycloakSession session; public void setModel(ComponentModel model) { this.model = model; } public void setSession(KeycloakSession session) { this.session = session; } @Remove @Override public void close() { } ... }
You have to define the @Local
annotation and specify your provider class there. If you don’t do this, EJB will not proxy the provider instance correctly and your provider won’t work.
You must put the @Remove
annotation on the close()
method of your provider. If you don’t, the stateful bean will never be cleaned up and you may eventually see error messages.
Implementations of ProviderFactory
are required to be plain java objects. Your factory class would perform a JNDI lookup of the Stateful EJB in its create() method.
public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> { @Override public EjbExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) { try { InitialContext ctx = new InitialContext(); EjbExampleUserStorageProvider provider = (EjbExampleUserStorageProvider)ctx.lookup( "java:global/user-storage-jpa-example/" + EjbExampleUserStorageProvider.class.getSimpleName()); provider.setModel(model); provider.setSession(session); return provider; } catch (Exception e) { throw new RuntimeException(e); } }
6.4. Available SPIs
If you want to see list of all available SPIs at runtime, you can check Server Info
page in admin console as described in Admin Console section.