3816154dc9
Closes #1688
397 lines
14 KiB
Text
397 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_spi]]
|
|
=== 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: {project_name} 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
|
|
----
|
|
|
|
To configure your provider, see the link:https://www.keycloak.org/server/configuration-provider[Configuring Providers] guide.
|
|
|
|
For example, to configure a provider you can set options as follows:
|
|
|
|
[source,bash]
|
|
----
|
|
bin/kc.[sh|bat] --spi-theme-selector-my-theme-selector-enabled=true --spi-theme-selector-my-theme-selector-theme=my-theme
|
|
----
|
|
|
|
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 the 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 (for example, 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;
|
|
}
|
|
}
|
|
----
|
|
|
|
[[_use_available_providers]]
|
|
=== Use available providers
|
|
|
|
In your provider implementation, you can use other providers available in {project_name}. The existing providers can be typically retrieved with the
|
|
usage of the `KeycloakSession`, which is available to your provider as described in the section <<_implementing_spi,Implementing an SPI>>.
|
|
|
|
{project_name} has two provider types:
|
|
|
|
* *Single-implementation provider types* - There can be only a single active implementation of the particular provider type in {project_name} runtime.
|
|
+
|
|
For example `HostnameProvider` specifies the hostname to be used by {project_name} and that is shared for the whole {project_name} server.
|
|
Hence there can be only single implementation of this provider active for the {project_name} server. If there are multiple provider implementations available to the server runtime,
|
|
one of them needs to be specified as the default one.
|
|
|
|
For example such as:
|
|
[source,bash]
|
|
----
|
|
bin/kc.[sh|bat] build --spi-hostname-provider=default
|
|
----
|
|
|
|
The value `default` used as the value of `default-provider` must match the ID returned by the `ProviderFactory.getId()` of the particular provider factory implementation.
|
|
In the code, you can obtain the provider such as `keycloakSession.getProvider(HostnameProvider.class)`
|
|
|
|
* *Multiple implementation provider types* - Those are provider types, that allow multiple implementations available and working together
|
|
in the {project_name} runtime.
|
|
+
|
|
For example `EventListener` provider allows to have multiple implementations available and registered, which means
|
|
that particular event can be sent to all the listeners (jboss-logging, sysout etc). In the code, you can obtain a specified instance of the provider
|
|
for example such as `session.getProvider(EventListener.class, "jboss-logging")` . You need to specify `provider_id` of the provider as the second argument
|
|
as there can be multiple instances of this provider type as described above.
|
|
+
|
|
The provider ID must match the ID returned by the `ProviderFactory.getId()` of the
|
|
particular provider factory implementation. Some provider types can be retrieved with the usage of `ComponentModel` as the second argument and some (for example `Authenticator`) even
|
|
need to be retrieved with the usage of `KeycloakSessionFactory`. It is not recommended to implement your own providers this way as it may be deprecated in the future.
|
|
|
|
=== Registering provider implementations
|
|
|
|
Providers are registered with the server by simply copying them to the `providers` directory.
|
|
|
|
If your provider needs additional dependencies not already provided by Keycloak copy these to the `providers` directory.
|
|
|
|
After registering new providers or dependencies Keycloak needs to be re-built with the `kc.[sh|bat] build` command.
|
|
|
|
==== Disabling a provider
|
|
|
|
You can disable a provider by setting the enabled attribute for the provider to false.
|
|
For example to disable the Infinispan user cache provider use:
|
|
|
|
[source,bash]
|
|
----
|
|
bin/kc.[sh|bat] build --spi-user-cache-infinispan-enabled=false
|
|
----
|
|
|
|
[[_script_providers]]
|
|
=== JavaScript providers
|
|
|
|
{project_name} has the ability to execute scripts during runtime in order to allow administrators to customize specific functionalities:
|
|
|
|
* Authenticator
|
|
* JavaScript Policy
|
|
* OpenID Connect Protocol Mapper
|
|
* SAML Protocol Mapper
|
|
|
|
==== Authenticator
|
|
|
|
Authentication scripts must provide at least one of the following functions:
|
|
`authenticate(..)`, which is called from `Authenticator#authenticate(AuthenticationFlowContext)`
|
|
`action(..)`, which is called from `Authenticator#action(AuthenticationFlowContext)`
|
|
|
|
Custom `Authenticator` should at least provide the `authenticate(..)` function.
|
|
You can use the `javax.script.Bindings` script within the code.
|
|
|
|
`script`::
|
|
the `ScriptModel` to access script metadata
|
|
`realm`::
|
|
the `RealmModel`
|
|
`user`::
|
|
the current `UserModel`
|
|
`session`::
|
|
the active `KeycloakSession`
|
|
`authenticationSession`::
|
|
the current `AuthenticationSessionModel`
|
|
`httpRequest`::
|
|
the current `org.jboss.resteasy.spi.HttpRequest`
|
|
`LOG`::
|
|
a `org.jboss.logging.Logger` scoped to `ScriptBasedAuthenticator`
|
|
|
|
NOTE: You can extract additional context information from the `context` argument passed to the `authenticate(context)` `action(context)` function.
|
|
|
|
[source,javascript]
|
|
----
|
|
AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");
|
|
|
|
function authenticate(context) {
|
|
|
|
LOG.info(script.name + " --> trace auth for: " + user.username);
|
|
|
|
if ( user.username === "tester"
|
|
&& user.getAttribute("someAttribute")
|
|
&& user.getAttribute("someAttribute").contains("someValue")) {
|
|
|
|
context.failure(AuthenticationFlowError.INVALID_USER);
|
|
return;
|
|
}
|
|
|
|
context.success();
|
|
}
|
|
----
|
|
|
|
==== Create a JAR with the scripts 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-script-authenticator.js",
|
|
"description": "My Authenticator from a JS file"
|
|
}
|
|
],
|
|
"policies": [
|
|
{
|
|
"name": "My Policy",
|
|
"fileName": "my-script-policy.js",
|
|
"description": "My Policy from a JS file"
|
|
}
|
|
],
|
|
"mappers": [
|
|
{
|
|
"name": "My Mapper",
|
|
"fileName": "my-script-mapper.js",
|
|
"description": "My Mapper from a JS file"
|
|
}
|
|
],
|
|
"saml-mappers": [
|
|
{
|
|
"name": "My Mapper",
|
|
"fileName": "my-script-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
|
|
+
|
|
* `saml-mappers`
|
|
+
|
|
For SAML Script Protocol Mappers. You can have one or multiple mappers in the same JAR file
|
|
|
|
For each script file in your `JAR` file, you need 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 {project_name} `providers/` directory, then run `bin/kc.[sh|bat] build`.
|
|
|
|
===== Deploy the script engine on Java 15 and later
|
|
|
|
To run the scripts, JavaScript providers require that a JavaScript engine is available in your Java application. Java 14 and lower versions include the Nashorn JavaScript Engine. It is
|
|
automatically available as part of the Java itself and JavaScript providers are able to use this script engine by default. However, for Java 15 or higher versions, the script engine is not part
|
|
of the Java itself. It needs to be added to your server because {project_name} does not have any script engine by default. Java 15 and higher versions require an extra step when deploying script
|
|
providers - adding the script engine of your choice to your distribution.
|
|
|
|
You can use any script engine. However, we only test with the Nashorn JavaScript Engine. The following steps assume that this engine is used:
|
|
|
|
Install the script engine by copying the nashorn script engine JAR and its dependencies directly to the `KEYCLOAK_HOME/providers` directory. In the `pom.xml` file
|
|
of your script project, you can declare the dependency such as this in the `dependencies` section:
|
|
|
|
```xml
|
|
<dependency>
|
|
<groupId>org.openjdk.nashorn</groupId>
|
|
<artifactId>nashorn-core</artifactId>
|
|
<version>15.3</version>
|
|
</dependency>
|
|
```
|
|
|
|
and declare `maven-dependency-plugin` in the `plugins` section to copy the dependencies to the specified directory:
|
|
|
|
```xml
|
|
<plugin>
|
|
<groupId>org.apache.maven.plugins</groupId>
|
|
<artifactId>maven-dependency-plugin</artifactId>
|
|
<executions>
|
|
<execution>
|
|
<id>copy-dependencies-quarkus</id>
|
|
<phase>package</phase>
|
|
<goals>
|
|
<goal>copy-dependencies</goal>
|
|
</goals>
|
|
<configuration>
|
|
<outputDirectory>${project.build.directory}/keycloak-server-copy/providers</outputDirectory>
|
|
<includeArtifactIds>nashorn-core,asm,asm-util,asm-commons</includeArtifactIds>
|
|
</configuration>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
```
|
|
Once the project is built, copy the script engine and its dependencies to the `KEYCLOAK_HOME/providers` directory.
|
|
```bash
|
|
cp target/keycloak-server-copy/providers/*.jar KEYCLOAK_HOME/providers/
|
|
```
|
|
After re-augment the distribution with `kc.sh build`, the script engine should be deployed and your script providers should work.
|
|
|
|
=== 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.
|