Creating Custom IdP Extensions - General Overview
This document is meant to give developers a basic overview in creating Shibboleth extensions. The general steps are the same for every plugin, only things like the particular classes and schema types that are extended change.
Be aware that the IdP uses the same plugin mechanism as custom extensions do in order to provide default implementations of the extension points. The only difference is that the files for those extensions are included in the IdP's jars instead of a separate one for the custom extension.
Appendix A provides a complete list of the extension points within the IdP. Following the link to the extension-specific description will provide the various namespaces, schema file locations, and classes needed when creating a custom extension.
The SVN repository shib-extension contains some example extensions.
What can be Extended?
The easiest way to tell what can be extended is to look in your IdP's configuration file. Every element with the attribute
xsi:type represents a plugin point.
As mentioned before the IdP uses the same plugin mechanisms for its own implementations. Therefore looking at the existing IdP implementations of the extension point should help provide already working code examples.
What does a Plugin Look Like?
The final result of a plugin is a JAR with the following structure:
While the schema file(s) for the extension do not have to go in a
schema directory it is recommended for organizational purposes.
Step 1: Set up the Project
The final result of an extension project is a JAR in the form outlined in the previous section. Any build environment may be used to create this JAR but these instructions will use Maven, the build system used by the IdP.
- Download this project template setup and extract it
- Edit the included
- Fill in the
<name>elements (found as children of
- Fill in the
<version> elementof the shibboleth-identityprovider dependency with the appropriate Shibboleth IdP version, e.g. "2.4.0" (found as a descendant of
- Fill in the
- Optionally, within
pom.xmluncomment some or all of the project metadata section at the bottom of the file and fill it in with the appropriate information
This guide assumes the usage of this project layout. It will refer to the root of the project folder as $PROJ_HOME.
Step 2: Write the Extension
Create a Java class that implements/extends the appropriate interface/class for the extension point being implemented.
Spring works by injecting properties into the plugin by either passing them as constructor arguments or by means of setter methods. That is, it assumes the plugin is a JavaBean. The example in this guide uses both constructor and property based injection.
Step 3: Define a Plugin Schema
The next step is to describe the properties needed by the plugin with an XML schema. This Schema enumerates all the possible configuration options for your extension and allows the IdP to perform basic validation of a user's configuration file.
The contents of the schema file will be one or more
<complexType> elements that extend a type defined either in the standard Shibboleth XML Schema files or another developer's extension.
Here are the basic steps for setting up the schema file:
- Pick a namespace for the plugin. Do NOT use the
- Create the schema file in
- Set the
targetNamespaceto the chosen namespace
- Declare any Shibboleth namespaces that will be used via the customary
xmlns:PREFIXattributes. At a minimum the namespace associated with the extension point must be declared.
- Set the
elementFormDefaultattribute value to "qualified"
- Set the
- Import the schema file that contains the plugin point being implemented. Shibboleth defines a special URL scheme,
classpath, which ensures that the schema files are resolved from the classpath. No other resolution mechanism is allowed.
- Define the plugin type(s) and ensure they extend the appropriate Shibboleth type.
If you are using Eclipse, you might want it to load an URL Handler for the "classpath:" URL scheme, so that Eclipse can find imported schemas when editing a .xsd file. You can download this JAR file and load it in Eclipse like this (assuming you downloaded the file in $HOME/shibboleth):
Step 4: Write the Spring BeanDefinitionParser
The Spring BeanDefinitionParser reads the XML configuration for the extension and sets the appropriate bean properties and/or constructor arguments on the plugin class. In many cases the IdP uses a slightly more complex method for creating its plugins. It uses a BeanDefinitionParser to populate an AbstractFactoryBean. The factory bean provides the JavaBean interface needed by Spring but allows those properties to be mangled or added to a plugin by means of some method other than setters. Within the IdP this is almost always done to avoid having to have setter methods that would replace some Collection field within the plugin (which can lead to various NullPointerExceptions, synchronization issues, etc.).
- Create a class that extends the Bean Parser class for the extension point.
- Define a
public static final QName SCHEMA_TYPEclass field that contains the QName for the extension type. The example uses a static variable from the class to be defined next. This QName isn't required but putting it here helps keep related information together.
- Implement the
getBeanClass(Element)method and return a
Classobject for the extension written in step 1.
- Implement the
doParse(Element, ParserContext, BeanDefinitionBuilder)method and populate the
BeanDefinitionBuilderwith information from the given
Elementobject is the DOM Element, matching the defined schema, for a particular configuration instance of the extension.
org.opensaml.xml.util.DatatypeHelper provide a lot of helper methods for dealing with XML and various data types. For example,
XMLHelper contains methods for getting attribute values as booleans, Lists, and other things.
Step 5: Define a NamespaceHandler
The last class to implement for Spring is the
NamespaceHandler. This is the class that instructs Spring to execute the
BeanDefinitionParser associated with a given extension type in a configuration file.
- Create a class that extends
- Add a public static final String NAMESPACE field whose value is the Schema namespace for your extension
- Implement the
init()method and, for each plugin within this namespace, call
registerBeanDefinitionParser(QName, BeanDefinitionParser)where the first argument is the
SCHEMA_TYPEfield on a
BeanDefinitionParserimplementation and the second argument is a new'ed instance of that
Step 6: Create Spring Configuration Files
Finally two files;
spring.handlers. Both files go in to the META-INF directory of the JAR and the
$PROJ_HOME/main/resources/META-INF directory within the project. Both files may have multiple, line-terminated, entries.
spring.schemas file tells Spring where to look for the schema file for a particular namespace. The format of the file is simply the XML namespace URI followed by an "=" followed by the location of the schema file within the JAR (i.e
schema/FILE.xsd). Do not include the a preceding "/", the files are not at the root of the filesystem.
spring.handlers file tells Spring which
NamespaceHandler to use for a particular namespace. The format of the file is the XML namespace URI followed by an "=" followed by the fully qualified class name of the
Note that because Spring uses the
java.util.Properties class to load these files the ':' character must be escaped with a backslash. This character has a special meaning for property files loaded in this manner.
Step 7: Write Installation Instructions
The expected procedure for installing an extension in to the IdP is to have the deployer copy the necessary material in to the expanded IdP distribution archive and then re-run the IdP installation script. This ensures the libraries get bundled in to the WAR file and placed where command line tools may use them. However the exact installation steps will depend on the nature of the extension. For example, an extension that includes not only custom plugins that use the Spring architecture but also new Servlets/JSP pages will require the deployer to edit the
web.xml document and/or copy JSP files before rebuilding the war.
Appendix: Shibboleth IdP Extension Points
Plugin point for creating a new match function used by the Attribute Filter Policy policy requirement, permit value, and deny value rules.
Plugin point for creating new attribute resolver attribute definitions.
Plugin point for creating a new attribute resolver attribute encoder.
Plugin point for creating a new attribute resolver data connector.
Plugin point for creating a new attribute resolver principal connector.
Plugin point for creating a new profile handler.
Plugin point for creating a new login handler.
Plugin point for creating a new relying party profile configuration.
Plugin point for creating a new relying party metadata provider.
Plugin point for creating a new metadata provider filter.
Plugin point for creating a new plugin for reading credentials.
Plugin point for creating a new trust engine.
Plugin point for creating a new security policy.
Plugin point for creating a new security policy rule.
Plugin point for creating a new resource.
Plugin point for creating a new resource filter.
Plugin point for creating a new service.