Service Provider Interface (SPI) contracts and abstractions for the Keycloak identity and access management server enabling extensibility through custom providers
—
The provider framework is the foundation of Keycloak's extensibility system. It provides the core interfaces and patterns that enable developers to create custom providers for authentication, user storage, credential management, and other extension points.
Base interface that all Keycloak providers must implement.
public interface Provider {
/**
* Called when the provider is closed and should clean up any resources.
*/
void close();
}Factory interface for creating provider instances. One factory instance exists per server.
public interface ProviderFactory<T extends Provider> {
/**
* Creates a new provider instance for the given session.
*
* @param session the Keycloak session
* @return new provider instance
*/
T create(KeycloakSession session);
/**
* Called once when the factory is first created. Configuration is from keycloak_server.json.
*
* @param config configuration scope
*/
void init(Config.Scope config);
/**
* Called after all provider factories have been initialized.
*
* @param factory the session factory
*/
void postInit(KeycloakSessionFactory factory);
/**
* Called when the server shuts down.
*/
void close();
/**
* Returns the unique identifier for this provider factory.
*
* @return provider ID
*/
String getId();
/**
* Returns the execution order for this factory. Lower values execute first.
*
* @return order value (default: 0)
*/
default int order() {
return 0;
}
/**
* Returns metadata for configuration properties supported by this factory.
*
* @return list of configuration property metadata
*/
default List<ProviderConfigProperty> getConfigMetadata() {
return Collections.emptyList();
}
/**
* Declares dependencies on other providers. Ensures dependent providers
* are initialized before this factory's postInit() is called.
*
* @return set of provider classes this factory depends on
*/
default Set<Class<? extends Provider>> dependsOn() {
return Collections.emptySet();
}
}Defines a Service Provider Interface, linking provider and factory classes.
public interface Spi {
/**
* Whether this SPI is internal to Keycloak (not for external use).
*
* @return true if internal
*/
boolean isInternal();
/**
* Returns the name of this SPI.
*
* @return SPI name
*/
String getName();
/**
* Returns the provider interface class.
*
* @return provider class
*/
Class<? extends Provider> getProviderClass();
/**
* Returns the provider factory interface class.
*
* @return provider factory class
*/
Class<? extends ProviderFactory> getProviderFactoryClass();
/**
* Whether this SPI is enabled by default.
*
* @return true if enabled (default: true)
*/
default boolean isEnabled() {
return true;
}
}Describes configuration properties for provider factories.
public class ProviderConfigProperty {
public static final String BOOLEAN_TYPE = "boolean";
public static final String STRING_TYPE = "String";
public static final String PASSWORD = "Password";
public static final String LIST_TYPE = "List";
public static final String ROLE_TYPE = "Role";
public static final String CLIENT_LIST_TYPE = "ClientList";
private String name;
private String label;
private String helpText;
private String type;
private Object defaultValue;
private List<String> options;
private boolean secret;
public ProviderConfigProperty() {}
public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue) {
this.name = name;
this.label = label;
this.helpText = helpText;
this.type = type;
this.defaultValue = defaultValue;
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
public String getHelpText() { return helpText; }
public void setHelpText(String helpText) { this.helpText = helpText; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public Object getDefaultValue() { return defaultValue; }
public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; }
public List<String> getOptions() { return options; }
public void setOptions(List<String> options) { this.options = options; }
public boolean isSecret() { return secret; }
public void setSecret(boolean secret) { this.secret = secret; }
}Marker interface for providers that require configuration.
public interface ConfiguredProvider {
// Marker interface - no methods required
}Base interface for provider events.
public interface ProviderEvent {
// Marker interface for provider events
}Listens for provider events.
public interface ProviderEventListener {
/**
* Called when a provider event occurs.
*
* @param event the provider event
*/
void onEvent(ProviderEvent event);
}Manages provider event listeners and dispatching.
public interface ProviderEventManager {
/**
* Registers an event listener.
*
* @param listener the event listener
*/
void register(ProviderEventListener listener);
/**
* Unregisters an event listener.
*
* @param listener the event listener
*/
void unregister(ProviderEventListener listener);
/**
* Publishes an event to all registered listeners.
*
* @param event the event to publish
*/
void publish(ProviderEvent event);
}// 1. Define your provider interface
public interface MyCustomProvider extends Provider {
void doSomething(String input);
String getSomething();
}
// 2. Implement the provider
public class MyCustomProviderImpl implements MyCustomProvider {
private final KeycloakSession session;
public MyCustomProviderImpl(KeycloakSession session) {
this.session = session;
}
@Override
public void doSomething(String input) {
// Implementation logic
}
@Override
public String getSomething() {
return "something";
}
@Override
public void close() {
// Cleanup resources
}
}
// 3. Create the provider factory
public class MyCustomProviderFactory implements ProviderFactory<MyCustomProvider> {
public static final String PROVIDER_ID = "my-custom";
@Override
public MyCustomProvider create(KeycloakSession session) {
return new MyCustomProviderImpl(session);
}
@Override
public void init(Config.Scope config) {
// Initialize from configuration
String setting = config.get("mySetting", "defaultValue");
}
@Override
public void postInit(KeycloakSessionFactory factory) {
// Post-initialization logic
}
@Override
public void close() {
// Cleanup on shutdown
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public List<ProviderConfigProperty> getConfigMetadata() {
List<ProviderConfigProperty> properties = new ArrayList<>();
properties.add(new ProviderConfigProperty(
"mySetting",
"My Setting",
"Description of my setting",
ProviderConfigProperty.STRING_TYPE,
"defaultValue"
));
return properties;
}
}
// 4. Define the SPI
public class MyCustomSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "my-custom";
}
@Override
public Class<? extends Provider> getProviderClass() {
return MyCustomProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return MyCustomProviderFactory.class;
}
}// Access a provider from KeycloakSession
KeycloakSession session = // obtain session
MyCustomProvider provider = session.getProvider(MyCustomProvider.class);
provider.doSomething("input");
String result = provider.getSomething();
// Access a specific provider implementation
MyCustomProvider provider = session.getProvider(MyCustomProvider.class, "my-custom");In keycloak-server.json:
{
"providers": [
{
"name": "my-custom",
"enabled": true,
"properties": {
"mySetting": "customValue"
}
}
]
}public class MyProviderFactory implements ProviderFactory<MyProvider> {
private volatile MyExternalService externalService;
@Override
public void init(Config.Scope config) {
// Initialize shared resources
String endpoint = config.get("endpoint");
this.externalService = new MyExternalService(endpoint);
}
@Override
public MyProvider create(KeycloakSession session) {
return new MyProviderImpl(session, externalService);
}
@Override
public void close() {
// Cleanup shared resources
if (externalService != null) {
externalService.shutdown();
}
}
}public class MyProviderFactory implements ProviderFactory<MyProvider> {
@Override
public Set<Class<? extends Provider>> dependsOn() {
return Set.of(OtherProvider.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
// Access dependent providers after they're initialized
try (KeycloakSession session = factory.create()) {
OtherProvider other = session.getProvider(OtherProvider.class);
// Use the other provider for initialization
}
}
}@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return List.of(
new ProviderConfigProperty("url", "Service URL", "URL of the external service",
ProviderConfigProperty.STRING_TYPE, null),
new ProviderConfigProperty("timeout", "Timeout", "Connection timeout in seconds",
ProviderConfigProperty.STRING_TYPE, "30"),
new ProviderConfigProperty("enabled", "Enabled", "Enable this provider",
ProviderConfigProperty.BOOLEAN_TYPE, true)
);
}Install with Tessl CLI
npx tessl i tessl/maven-org-keycloak--keycloak-server-spi@26.2.1