How to implement a Service Provider Interface (SPI) and package a JAR
This guide explains the development and packaging of Java plugins for the Connect2id server.
The server exposes over a dozen plugin interfaces, called Service Provider Interfaces (SPI) in Java. The SPI is a standard Java facility for dynamic runtime loading of bytecode that implements an interface contract. The implemented code can be packaged in a JAR file for convenience.
1. Set up your project
Create a new Java project with the appropriate packaging, Java version and dependencies.
Packaging
The project must create a JAR package.
Java version
Should be set to the Java version required by the Connect2id server. At the time of writing this guide this is Java 11 or 17.
Dependencies
Import the following dependencies:
<dependencies>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>c2id-server-sdk</artifactId>
<version>[version]</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>[version]</version>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.9</version>
<optional>true</optional>
</dependency>
</dependencies>
The com.nimbusds:c2id-server-sdk
and com.nimbusds:oauth2-oidc-sdk
versions
should match those in the targeted minimal Connect2id server version.
How to find out the SDK versions?
Inspect the content of the c2id.war
(a ZIP file) where a copy of the server's
POM is stored under /META-INF/maven/com.nimbusds/c2id-server/pom.xml
.
Several transitive dependencies will also be imported, such as those for the Nimbus JOSE+JWT library.
Shading dependencies to prevent conflicts
If the plugin needs to import a dependency that may cause a library version conflict or a class naming conflict with the Connect2id server's own dependencies consider shading it. The Maven Shade Plugin is the recommended tool for this purpose.
Here is an example shading of the Gson library, which may have the following
import declaration in the plugin's pom.xml
:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
To shade it, import the Maven Shade Plugin. Specify 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.4.1</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 are free to utilise dependency injection internally.
Note that dependency injection may encounter issues with shaded dependencies. Such are typically resolved by configuring the dependency injection framework to use the shaded package names.
2. Programming
Implement the SPI for the plugin, which is defined in the Connect2id server SDK, and don't forget that the SDK has excellent JavaDocs too.
Git repo | https://bitbucket.org/connect2id/server-sdk |
---|
Annotate the implementing class with @MetaInfServices to have the appropriate SPI manifest file automatically generated and 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
}
For the above example the generated SPI manifest file will be located at
/META-INF/services/com.nimbusds.openid.connect.provider.spi.claims.ClaimsSource
and its content will be the class name of the implementation:
com.example.my.c2id.plugin.MyClaimsSource
3. Deployment
Add the generated JAR to the /WEB-INF/lib/
directory of the c2id.war
, then
deploy the WAR file to your server environment.
Some of the Connect2id server SPIs allow only a single plugin to be loaded, others multiple.
If the particular SPI contract requires there to be only one plugin instance
make sure there are no other plugin JARs for the SPI in the /WEB-INF/lib/
directory. If the distributed c2id.war
package includes one remove it.
If the single plugin instance for a given SPI is violated the Connect2id server will abort startup with an error.
4. Troubleshooting
The Connect2id server will log the loading of each plugin under a unique code given in the plugin documentation. You can use this code to search the logs to quickly find out if your implementation was correctly loaded.
Examples:
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
We also recommended that your plugin logs conditions and usage for debugging and security audit where necessary.
The Connect2id server uses the Log4j framework.
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>[version]</version>
</dependency>
The Log4j version can be found out from the Connect2id server POM, as explained above.