The Shibboleth Identity Provider uses Spring and XML and XML Schema extensively. Before you begin you must have a basic understanding of these technologies. |
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.
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.
The final result of a plugin is a JAR with the following structure:
/META-INF/spring.handlers /META-INF/spring.schemas /schema/... your schema files ... ... Java class files for extension... |
While the schema file(s) for the extension do not have to go in a schema
directory it is recommended for organizational purposes.
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.
pom.xml
<groupId>
, <artifactId>
, <version>
, <name>
elements (found as children of <project>)
<version> element
of the shibboleth-identityprovider dependency with the appropriate Shibboleth IdP version, e.g. "2.4.0" (found as a descendant of <project>/<dependencies>)
pom.xml
uncomment some or all of the project metadata section at the bottom of the file and fill it in with the appropriate informationThis guide assumes the usage of this project layout. It will refer to the root of the project folder as $PROJ_HOME.
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.
package org.example.shibboleth.authn; ... imports ... public class IPAddressLoginHandler extends AbstractLoginHandler { public IPAddressLoginHandler(String username){ // method logic } /** {@inheritDoc} */ public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { // method logic } public List<String> getAllowedIPAddressRanges(){ // method logic } public void setAllowedIPAddressRanges(List<String> ranges){ // method logic } } |
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:
urn:mace:shibboleth
namespace.$PROJ_HOME/resources/schema
targetNamespace
to the chosen namespacexmlns:PREFIX
attributes. At a minimum the namespace associated with the extension point must be declared.elementFormDefault
attribute value to "qualified"classpath
, which ensures that the schema files are resolved from the classpath. No other resolution mechanism is allowed.<schema targetNamespace="urn:mace:example.org:shibboleth:authn" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:ph="urn:mace:shibboleth:2.0:idp:profile-handler" elementFormDefault="qualified"> <import namespace="urn:mace:shibboleth:2.0:idp:profile-handler" schemaLocation="classpath:/schema/shibboleth-2.0-idp-profile-handler.xsd" /> <complexType name="IPAddress"> <complexContent> <extension base="ph:LoginHandlerType"> <sequence> <element name="IPEntry" type="string" maxOccurs="unbounded" /> </sequence> <attribute name="username" type="string" /> </extension> </complexContent> </complexType> </schema> |
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):
eclipse -vmargs -Djava.protocol.handler.pkgs=org.my.protocols -Xbootclasspath/p:$HOME/shibboleth/classpath-uri-stream-handler-0.0.1-SNAPSHOT.jar |
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.).
public static final QName SCHEMA_TYPE
class 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.getBeanClass(Element)
method and return a Class
object for the extension written in step 1.doParse(Element, ParserContext, BeanDefinitionBuilder)
method and populate the BeanDefinitionBuilder
with information from the given Element
. This Element
object is the DOM Element, matching the defined schema, for a particular configuration instance of the extension.package org.example.shibboleth.authn.config; ... imports ... public class IPAddressLoginHandlerBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser{ /** Schema type. */ public static final QName SCHEMA_TYPE = new QName(AuthnExtensionNamespaceHandler.NAMESPACE, "IPAddress"); /** {@inheritDoc} */ protected Class getBeanClass(Element arg0) { return IPAddressLoginHandler.class; } /** {@inheritDoc} */ protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { // Create a list from the <IPEntry> elements List ipEntries; // code for populating the list omitted builder.addPropertyValue("allowedIPAddressRanges", ipEntries); String username = element.getAttributeNS(null, "username"); builder.addConstructorArg(username); } } |
The classes |
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.
edu.internet2.middleware.shibboleth.common.config.BaseSpringNamespaceHandler
init()
method and, for each plugin within this namespace, call registerBeanDefinitionParser(QName, BeanDefinitionParser)
where the first argument is the SCHEMA_TYPE
field on a BeanDefinitionParser
implementation and the second argument is a new'ed instance of that BeanDefinitionParser
.package org.example.shibboleth.authn.config; ... imports ... public class AuthnNamespaceHandler extends BaseSpringNamespaceHandler { public static final String NAMESPACE = "urn:mace:example.org:shibboleth:authn"; public void init() { registerBeanDefinitionParser(IPAddressLoginHandlerBeanDefinitionParser.SCHEMA_TYPE, new IPAddressLoginHandlerBeanDefinitionParser()); // other calls to registerBeanDefinitionParser for other extensions in this namespace would go here } } |
Finally two files; spring.schemas
and 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.
The 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.
urn\:mace\:example.org\:shibboleth\:authn = schema/authn.xsd |
The 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 NamespaceHandler
implementation.
urn\:mace\:example.org\:shibboleth\:authn = org.example.shibboleth.authn.config.AuthnNamespaceHandler |
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.
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.
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. |