keycloak-scim/server_development/topics/providers.adoc

412 lines
14 KiB
Text

[[_providers]]
== Service Provider Interfaces (SPI)
{project_name} is designed to cover most use-cases without requiring custom code, but we also want it to be customizable.
To achieve this {project_name} has a number of Service Provider Interfaces (SPI) for which you can implement your own providers.
=== Implementing an 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:
[source,java]
----
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";
}
}
----
NOTE: 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 request so these should be light-weight object.
Example ThemeSelectorProvider:
[source,java]
----
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`):
[source]
----
org.acme.provider.MyThemeSelectorProviderFactory
----
You can configure your provider through `standalone.xml`, `standalone-ha.xml`, or `domain.xml`.
For example by adding the following to `standalone.xml`:
[source,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:
[source,java]
----
public void init(Config.Scope config) {
String themeName = config.get("theme");
}
----
Your provider can also lookup other providers if needed. For example:
[source,java]
----
public class MyThemeSelectorProvider implements ThemeSelectorProvider {
private KeycloakSession session;
public MyThemeSelectorProvider(KeycloakSession session) {
this.session = session;
}
@Override
public String getThemeName(Theme.Type type) {
return session.getContext().getRealm().getLoginTheme();
}
}
----
[[_providers_admin_console]]
==== Show info from your SPI implementation in admin console
Sometimes it is useful to show additional info about your Provider to a {project_name} administrator. You can show provider build time information (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). {project_name} 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:
[source,java]
----
package org.acme.provider;
import ...
public class MyThemeSelectorProviderFactory implements ThemeSelectorProviderFactory, ServerInfoAwareProviderFactory {
...
@Override
public Map<String, String> getOperationalInfo() {
Map<String, String> ret = new LinkedHashMap<>();
ret.put("theme-name", "my-theme");
return ret;
}
}
----
=== Registering provider implementations
There are two ways to register provider implementations. In most cases the simplest way is to use the {project_name} 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 {project_name} deployer approach.
==== Using the {project_name} Deployer
If you copy your provider jar to the {project_name} `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 {appserver_name}
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.
==== 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:
[source]
----
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:
[source,xml]
----
<?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 {project_name}.
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:
[source,xml]
----
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>module:org.keycloak.examples.event-sysout</provider>
</providers>
...
----
==== 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:
[source,xml]
----
<spi name="userCache">
<provider name="infinispan" enabled="false"/>
</spi>
----
=== 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 party 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 {appserver_name} environment. See the {appserver_name} 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:
[source,java]
----
@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.
[source,java]
----
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);
}
}
----
[[_script_providers]]
=== JavaScript Providers
{project_name} has the ability to execute scripts during runtime in order to allow administrators to customize specific functionalities:
* OpenID Connect Script Protocol Mapper
* OpenID Connect Script Authenticator
* JavaScript Policy
==== Create a JAR with the scripts you want to deploy
NOTE: JAR files are regular ZIP files with a `.jar` extension.
In order to make your scripts available to {project_name} you need to deploy them to the server. For that, you should create
a `JAR` file with the following structure:
[source]
----
META-INF/keycloak-scripts.json
my-script-authenticator.js
my-script-policy.js
my-script-mapper.js
----
The `META-INF/keycloak-scripts.json` is a file descriptor that provides metadata information about the scripts you want to deploy. It is a JSON file with the following structure:
```json
{
"authenticators": [
{
"name": "My Authenticator",
"fileName": "my-authenticator.js",
"description": "My Authenticator from a JS file"
}
],
"policies": [
{
"name": "My Policy",
"fileName": "my-policy.js",
"description": "My Policy from a JS file"
}
],
"mappers": [
{
"name": "My Mapper",
"fileName": "my-mapper.js",
"description": "My Mapper from a JS file"
}
]
}
```
This file should reference the different types of script providers that you want to deploy:
* `authenticators`
+
For OpenID Connect Script Authenticators. You can have one or multiple authenticators in the same JAR file
+
* `policies`
+
For JavaScript Policies when using {project_name} Authorization Services. You can have one or multiple policies in the same JAR file
+
* `mappers`
+
For OpenID Connect Script Protocol Mappers. You can have one or multiple mappers in the same JAR file
For each script file in your `JAR` file you must have a corresponding entry in `META-INF/keycloak-scripts.json` that maps your scripts files to a specific provider type. For that you should provide the following properties for each entry:
* `name`
+
A friendly name that will be used to show the scripts through the {project_name} Administration Console. If not provided, the name
of the script file will be used instead
+
* `description`
+
An optional text that better describes the intend of the script file
+
* `fileName`
+
The name of the script file. This property is *mandatory* and should map to a file within the JAR.
==== Deploy the Script JAR
Once you have a JAR file with a descriptor and the scripts you want to deploy, you just need to copy the JAR to the to the {project_name} `standalone/deployments/` directory.
==== Using {project_name} Administration Console to upload scripts
By default, the {project_name} Administration Console won't allow administrators to upload scripts to the server. The reason is that
script execution can potentially harm the system due to the execution of malicious scripts. In order to prevent different types of
attacks and lower the risks when running scripts at runtime, administrators should prefer deploying scripts directly to the server using a JAR file.
By deploying scripts directly into the server, we expect that scripts go through a proper code analysis in order to find possible
vulnerabilities that can be introduced by any script deployed to the server.
However, it should still be possible to upload scripts to the server through the {project_name} Administration Console. For that,
you must set the following system property when booting the server:
```bash
-Dkeycloak.profile.feature.upload_scripts=enabled
```
For more details about how to enable the `upload_scripts` feature. Please, take a look at the link:{installguide_profile_link}[{installguide_profile_name}].
=== 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 <<_providers_admin_console,Admin Console>> section.