Plugins#
The functionality of the Curity Identity Server can be extended through plugins.
A plugin is basically a set of JARs containing implementations of the Curity Identity Server‘s extension points, i.e. sub-interfaces of PluginDescriptor, along with any accompanying library jars and other resources (message files, templates, assets).
The sections below present a high-level overview of the Curity Identity Server‘s Plugin System. For more details, see the plugin SDK documentation.
Access to the Curity Release Repository#
The SDK JAR, which your plugin will depend on, can be found in Maven Central and other public Maven repositories.
Plugin Installation#
Plugins are installed in groups at ${IDSVR_HOME}/usr/share/plugins/<plugin_group>.
To install a plugin in the Curity Identity Server, simply drop its jars and all of its required resources,
including Server-Provided Dependencies , in the <plugin_group> directory.
Sub-directories are not taken into consideration.
The actual names of the <plugin_group> directories have no meaning for the Curity Identity Server. You may place any number of plugins in the same plugin group, allowing them to share the same classpath (see the Classpath Considerations section for details).
Also, you may create any number of plugin groups.
We advise that you only split up plugins into separate groups if you need complete isolation (classpath separation) between them.
Classpath considerations#
Each plugin group is given its own Java ClassLoader, meaning that their classpath are almost 2 completely isolated from
the Curity Identity Server‘s and other plugin groups’s classpath.
This isolation requires all runtime dependencies, except for the Curity Identity Server plugin SDK library and Server-Provided Dependencies , to be included in the plugin group directory alongside the plugin’s own resources.
On startup, the Curity Identity Server scans each plugin group directory for added/removed/modified plugins (notice that this implies that modifications to plugin resources require a server restart). At this point, the Curity Identity Server compiles the configuration definition for any added/modified plugins. If it encounters any invalid plugin configuration definition, the Curity Identity Server will not start.
Once installed, you can configure a plugin using the the Curity Identity Server‘s standard configuration mechanisms. See the Configuration Guide for details.
Please note that plugin updates that contain modified configuration definitions may break existing server configuration, which will cause a halt at startup. It is therefore recommended to remove any configured references to a plugin before updating it and restarting the Curity Identity Server.
If a plugin loads Java types at run time, or uses frameworks that do so (e.g. JAXB, JAF, JAX-WS), it may be necessary
to modify the Java context ClassLoader so that the Curity Identity Server finds all types within the plugin and its dependencies.
To assist with that, use the se.curity.identityserver.sdk.ClassLoaderContextUtils type as shown below:
new MyPluginHandler<>()
{
@Override
public boolean handleMessage(SOAPMessageContext context)
{
return ClassLoaderContextUtils.fromClass(MyPluginHandler.class)
.withPluginClassLoader(() -> {
// because this causes Jakarta EE Services to be loaded, this call
// must be made inside a `withPluginClassLoader` lambda.
SOAPMessage soapMessage = context.getMessage();
return ...;
});
}
...
}
Basic structure of a plugin#
A plugin’s entry-point is its PluginDescriptor that is a class that describes
the plugin type (given by which PluginDescriptor subtype the plugin descriptor implements),
what services the plugin provides, as well as its Configuration interface.
The Configuration interface is used to generate the plugin’s configuration at runtime. An instance of the plugin’s
Configuration interface is generated by the Curity Identity Server based on the server’s current configuration state and provided to the
plugin’s code via constructor-injection (see the Plugin Example ).
The Curity Identity Server finds plugin descriptor implementation classes using the standard Java ServiceLoader mechanism. That is it looks at files named META-INF/services/<service-interface>, where <service-interface> should be
replaced with the fully qualified name of the service interface, within the jars.
The contents of these files should be the name(s) of the implementation class(es) for the service interface.
If more than one implementation exists, each implementation class should be given on each line.
If this file is missing, the Curity Identity Server will not recognize the plugin.
In summary, a plugin should normally contain, at least, the following:
- An (interface) extension of the Configuration interface.
- One or more service implementations, as specified by the plugin descriptor.
- An implementation of one or more of the PluginDescriptor subtypes.
- A Service loader (
META-INF/services/<service-interface>) file.
SmsSender Plugin Example#
Plugin Configuration interface:
package com.example;
import se.curity.identityserver.sdk.config.Configuration;
import se.curity.identityserver.sdk.service.HttpClient;
import java.net.URI;
public interface ExampleSmsConfiguration extends Configuration {
// plugin configuration primitives
URI getServerUri();
int getMaxReconnectAttempts();
// any services required by the plugin
HttpClient getHttpClient();
}
SmsSender Service implementation:
package com.example;
import se.curity.identityserver.sdk.service.sms.SmsSender;
public class ExampleSmsSender implements SmsSender {
private final ExampleSmsConfiguration configuration;
// obtain the current configuration via constructor-injection
public ExampleSmsSender(ExampleSmsConfiguration configuration) {
this.configuration = configuration;
}
@Override
public boolean sendSms(String toNumber, String msg) {
// add SMS sending logic here
return false;
}
}
PluginDescriptor implementation:
package com.example;
import se.curity.identityserver.sdk.plugin.descriptor.SmsPluginDescriptor;
import se.curity.identityserver.sdk.service.sms.SmsSender;
import se.curity.identityserver.sdk.config.Configuration;
public class ExampleSmsPluginDescriptor implements SmsPluginDescriptor<ExampleSmsConfiguration> {
@Override
public String getPluginImplementationType() {
return "example-sms";
}
@Override
public Class<ExampleSmsConfiguration> getConfigurationType() {
return ExampleSmsConfiguration.class;
}
@Override
public Class<? extends SmsSender> getSmsSenderType() {
return ExampleSmsSender.class;
}
}
Service loader file:
com.example.ExampleSmsPluginDescriptor
Managed Objects#
Plugins that require objects that must have a well-defined lifecycle can use the ManagedObject class.
A ManagedObject is an object that gets instantiated when the plugin gets loaded, and closed as soon as
configuration changes or the Curity Identity Server starts to shut down.
Managed objects are typically used to handle resources such as connection pools that the plugin may require.
To use a ManagedObject, you should override the createManagedObject() method in the plugin’s PluginDescriptor
and return a initialized instance of your managed object.
The close() method of the ManagedObject will be called when the Curity Identity Server needs to replace configuration or
shut down.
Plugin services may obtain an instance of the plugin’s ManagedObject via constructor-injection, in the exact same
way as they obtain the plugin’s Configuration instance.
Back-channel Authenticators that have a link to a front-channel authenticator plugin may also inject the
front-channel plugin’s ManagedObject (and Configuration), if it has one, in addition to its own.
Plugin Services#
The SDK of the Curity Identity Server provides several services that may be used by plugins.
All services are located within the se.curity.identityserver.sdk.service package and its sub-packages.
A plugin obtains a service by adding a getter that returns an instance of the Service to the plugin-provided
Configuration interface.
For example, to obtain a SmsSender service, a plugin could declare the following getter in its Configuration
interface:
package com.example;
import se.curity.identityserver.sdk.config.annotation.Description;
import se.curity.identityserver.sdk.config.Configuration;
import se.curity.identityserver.sdk.service.sms.SmsSender;
public interface ExampleConfiguration extends Configuration {
@Description("SMS Sender configured for this plugin")
SmsSender getSmsSender();
}
A very common service is the Throttling Service , which can be used for rate-limiting various operations.
Service Restrictions by Plugin Type#
There are a few limitations to what services may be obtained by a plugin depending on its type, as shown in the following table (services not shown in this table may be used in plugins of any type):
| Service | Allowed Plugin Types |
|---|---|
AuthenticatorInformationProvider | Authenticator, BackchannelAuthenticator, AuthenticationAction |
AuthenticationActionInformationProvider | AuthenticationAction |
AuthenticatorDescriptorFactory | AuthenticationAction |
AuthenticatorDescriptor | AuthenticationAction |
AccountDomain | AuthenticationAction |
AuthenticatorExceptionFactory | Authenticator |
RequestingOAuthClient | Authenticator, BackchannelAuthenticator, AuthenticationAction |
AuthenticationRequirements | Authenticator, AuthenticationAction |
CredentialVerifier | Authenticator |
UserCredentialManager | Authenticator, AuthenticationAction |
all services under the SDK’s service.issuer package | TokenProcedure |
all services under the SDK’s service.introspecter package | TokenProcedure |
Service Restrictions in ManagedObject#
Managed Objects have limited access to services. Namely, they may only use services that are annotated
with the @ConfigurationScope annotation, as opposed to other plugin implementation objects in which such
limitations do not apply.
Because a service’s configuration may be modified independently of a Managed Object , it’s very important to
not cache a service within a ManagedObject (either instance fields or static fields, or anywhere else).
Within a ManagedObject, always access a service through the Configuration interface getter,
never cache services in fields or global variables.
For more details, consult the Javadocs for ManagedObject and ConfigurationScope.
Cross-site Plugin Handlers#
Some plugin types can expose request handlers (see RequestHandler interface) to process requests originating on the user’s browser. The access from cross-site origins to those handlers is controlled by the Curity Identity Server according to the plugin’s defined policy:
- Same-site access is always allowed.
- Cross-site access with safe HTTP methods (e.g.
GET) is always allowed. - Cross-site access with non-safe HTTP methods (e.g.
POST) depends on the plugin’s policy. For legacy reasons, by default all handlers allow cross-site access however this can be changed programmatically.
The plugin descriptor’s interface exposes an allowedHandlersForCrossSiteNonSafeRequests method returning a RequestHandlerSet, containing the set of handlers that can be called on cross-site requests.
The RequestHandlerSet class has the following static methods to help with instance creation:
RequestHandlerSet.all()- returns a set with all the plugin’s handlers.RequestHandlerSet.none()- returns an empty set.RequestHandlerSet.of(...)- returns a set with the given handlers.
This feature aims to provide protection against CSRF (Cross Site Request Forgery) attacks, by automatically blocking cross-site requests to handlers that don’t need this type of access. Plugins with handlers that need cross-site requests should have internal protection mechanisms against CSRF attacks.
Note also that this protection feature depends on the user’s browser supporting the SameSite cookie attribute feature. If the browser doesn’t support it, then all requests are classified as same-site and allowed into the handlers.
An HTTP request originating from an iframe hosted on a different origin will be considered cross-origin by a modern browser
and any cookies marked with the SameSite attribute set to lax or strict will not be sent.
This means that by default this protection mechanism will block plugin handlers from being accessible
inside iframes on browsers that support SameSite.
To enable framing, the plugin needs to explicitly allow the accessed handlers in the allowedHandlersForCrossSiteNonSafeRequests method.
If changing the plugin code is not possible, then see Cross-site Requests on ways to disable this protection mechanism
via system properties.
Java Version#
The only version of Java which the Curity Identity Server is verified to run on is the one that is bundled with it. That is the open-source Eclipse Temurin JDK distribution.
The current major Java version used is Java 21.
However, only a JRE (Java runtime environment containing only the Java modules used by the Curity Identity Server) is shipped, not the full JDK.
So, developers will need to obtain a full JDK (including javac and other such tools) distribution.
The recommended ones are:
If in doubt about which Java version is used by the distribution of the Curity Identity Server you are using,
check the file ${IDSVR_HOME}/lib/java/release.
Server-Provided Dependencies#
Plugins may have any dependencies, as long as all of the JARs are included with the plugin in the plugin group directory. However, when using one of the dependencies that are provided by the Curity Identity Server, the library versions must match exactly those that the Curity Identity Server provides. Because the version must exactly match the versions shipped with the Curity Identity Server, it is not recommended to deploy these dependencies with the plugin, but to instead have compile-time dependencies on the exact versions below.
If provided dependencies listed below are included in a plugin’s classpath anyway, and if a plugin uses a different version than what the Curity Identity Server provides, runtime errors may occur.
Including the JARs of these libraries in the plugin directory is allowed, and in such case the Curity Identity Server will check that their versions are correct, if possible. However, that is not guaranteed, especially if the dependencies are shaded - hence doing that is not recommended.
The following dependencies, besides Curity’s SDK, are provided by the Curity Identity Server. There is no guarantee that other dependency are available, including some parts of the Java runtime.
Any dependency that is not listed below is subject to change without notice (which may even come with breaking changes).
SLF4J Logging API#
| Description | The slf4j API |
| Bundle-SymbolicName | slf4j.api |
| Version | 2.0.12 |
| Required | No |
Plugins that use the slf4j-api or java.util.logging for logging can be configured via
the log4j2 configuration file of the Curity Identity Server.
Bean Validation API#
| Description | Jakarta Bean Validation API |
| Bundle-SymbolicName | jakarta.validation.jakarta.validation-api |
| Version | 3.0.0 |
| Required | No |
Validation annotations defined in this bundle may be used for decorating Request Models handled by a RequestHandler. Plugins depending on another version of this library bundle than the one specified here will be rejected at service boot; resulting in a controlled service halt.
Hibernate Validator Engine#
| Description | Hibernate Validator Engine |
| Bundle-SymbolicName | org.hibernate.validator |
| Version | 7.0.3.Final |
| Required | No |
See note for Bean Validation API dependency.
This dependency should only be added if annotations not present in jakarta.validation are desired.
Kotlin Standard Library#
| Description | Kotlin Standard Library |
| Implementation-Title | kotlin-stdlib |
| Version | 2.2.0 |
| Required | No |
All plugins written in Kotlin should include this dependency.
Serialization#
For security reasons, Java serialization and deserialization are restricted and cannot be used by default. This means that any class implementing the java.io.Serializable interface and using Java’s facilities to turn an object of such a kind to or from a byte stream will fail. Many known and unknown security vulnerabilities are prevented by this restriction, so changing it is ill advised. If it must be, however, Java’s standard system property can be set to include a pattern of allowed packages, classes, etc. that should be serializable and deserializable (Cf. JVM options ). Using this property of Java’s, however, needs to also include the internal classes and types that need to be serialized otherwise undefined behavior may result. Because these classes and types are undocumented and subject to change, it is better to use the dedicated system property se.curity:identity-server:serialFilter in place of Java’s jdk.serialFilter system property. This will add all internal classes and types plus those defined in the value of this property. The value fo the se.curity:identity-server:serialFilter system property is identical to Java’s jdk.serialFilter system property, so refer to the Java documentation for details.
Footnotes#
-
As long as the programming language’s compiler produces valid jars and it does not load classes dynamically or replace common Java types (such as
List,String,Map) with their own types, there should be no problems for plugins to interact with the Curity Identity Server. ↩ -
The Server-Provided Dependencies listed on this page are the only ones that are loaded under a common
ClassLoader, namely, the class loader of the Curity Identity Server, so the classpath isolation is not complete. ↩