How to implement a Service Provider Interface (SPI) and package a JAR
This guide explains how to develop and package Java plugins for the Connect2id server.
The server features a set of plugin interfaces, referred to
as Service Provider Interfaces (SPI) in Java. An
SPI is a
standard Java mechanism for dynamically loading implementations of an interface
at runtime. The plugin implementation is typically packaged in a JAR file.
1. Set up your project
Create a new Java project with the appropriate packaging, Java version and dependencies.
Packaging
The project must produce a JAR package.
Java version
Must not exceed the Java runtime version required by the Connect2id server. At the time of writing, this is Java 17.
Dependencies
Import the following dependencies:
<dependencies>
<dependency>
<!-- The Connect2id server SDK -->
<groupId>com.nimbusds</groupId>
<artifactId>c2id-server-sdk</artifactId>
<version>[version]</version>
</dependency>
<dependency>
<!-- The OAuth 2.0 / OpenID Connect SDK -->
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>[version]</version>
</dependency>
<dependency>
<!-- If the plugin needs to log messages -->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>[version]</version>
</dependency>
<dependency>
<!-- SPI annotation -->
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.11</version>
<optional>true</optional>
</dependency>
</dependencies>
The [version] values should match those used by the targeted minimum
Connect2id server version.
How to determine out the dependency versions?
Inspect the SBOM file (XML or JSON) included in
the c2id.war package (a ZIP archive), located under:
/WEB-INF/sbom
The project will also pull several transitive dependencies, such as those from the Nimbus JOSE+JWT library.
Shading dependencies to prevent conflicts
If the plugin depends on libraries that conflict with those bundled in the Connect2id server (for example, due to version or classpath clashes), those dependencies must be shaded.
The recommended tool is the Maven Shade Plugin.
Example using Gson:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency>
The plugin configuration specifies the artifact to shade and the relocation of its package(s). The relocation should typically be to the plugin’s own package namespace.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<artifactSet>
<includes>
<include>com.google.code.gson:gson</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>org.myplugin.shaded.gson</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
To shade multiple dependencies add as many artifactSet.includes.include and
relocations.relocation elements as necessary.
Dependency injection
Plugins may use dependency injection frameworks internally.
Be aware that shading can interfere with classpath scanning. If issues occur, configure the DI framework to use the relocated (shaded) package names.
2. Development
Implement the required SPI from the Connect2id server SDK.
The SDK JavaDocs provide detailed and up-to-date documentation.
| Git repo | https://bitbucket.org/connect2id/server-sdk |
|---|
Annotate the implementation class with @MetaInfServices to automatically generate the SPI manifest file and have it included in the JAR.
If the SPI manifest is missing, the plugin will not be loaded!
Example:
package com.example.my.c2id.plugin;
import org.kohsuke.MetaInfServices;
import com.nimbusds.openid.connect.provider.spi.claims.ClaimsSource;
@MetaInfServices
public class MyClaimsSource implements ClaimsSource {
// Implementing code
}
This generates the SPI manifest file:
/META-INF/services/com.nimbusds.openid.connect.provider.spi.claims.ClaimsSource
With the content:
com.example.my.c2id.plugin.MyClaimsSource
3. Deployment
Copy the plugin JAR into:
/WEB-INF/lib/
of the Connect2id server deployment.
If deploying as a WAR file (e.g. c2id.war), add the plugin JAR to this directory inside the archive.
4. Troubleshooting
Plugin loading logs
The Connect2id server logs each detected plugin with a unique code defined in the SPI documentation. This makes it easy to verify successful loading in the server logs.
Example:
2017-08-07T13:39:44,634 INFO localhost-startStop-1 MAIN - [OP7101] Loaded claims source [1]: class com.nimbusds.openid.connect.provider.spi.claims.ldap.LDAPClaimsSource
2017-08-07T13:39:44,635 INFO localhost-startStop-1 MAIN - [OP7102] Claims supported by source [1]: sub email_verified address x_employee_number name nickname phone_number_verified phone_number preferred_username given_name family_name email
Single vs multiple enabled plugins for an SPI
Some Connect2id server SPIs support only a single active plugin, while others support multiple. This is specified in the SPI documentation.
If for a given SPI multiple plugins are enabled, but the SPI expects at most one enabled plugin, the Connect2id server aborts startup with an error.
Logging
The Connect2id server uses the Log4j framework. Plugins can use Log4j to emit operational and diagnostic logs.