The Shibboleth V2 IdP and SP software have reached End of Life and are no longer supported. This documentation is available for historical purposes only. See the IDP v4 and SP v3 wiki spaces for current documentation on the supported versions.

IdPErrorVelocity

Error Handling with Velocity

The default error handling behavior in the IdP relies on a JSP page to display errors to the user. This requires reinstalling and restarting the IdP to make changes to the error handling or the look and feel of the page.

The IdP includes an alternative error handler plugin that allows a Velocity macro template to be used instead. A few small changes are required to enable effective use of this plugin. Do not take the examples literally. Your XML may be different depending on the namespace prefixes (or lack thereof) in your files.

In place of the default <ph:ErrorHandler> element in handler.xml something like the following is used:

handler.xml
<ph:ErrorHandler xsi:type="ph:VelocityErrorHandler" errorTemplatePath="error.vt" velocityEngine="shibboleth.MyVelocityEngine"/>

The reference to the velocity engine is an example. It must contain the ID of a bean you define in a custom Spring configuration file such as the following:

myext.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd" >

    <bean id="shibboleth.MyVelocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean" depends-on="shibboleth.LogbackLogging">
        <property name="overrideLogging" value="false"/>
        <property name="velocityProperties">
            <props>
                <prop key="runtime.log.logsystem.class">
                    edu.internet2.middleware.shibboleth.common.util.Slf4JLogChute
                </prop>
                <prop key="resource.loader">file</prop>
                <prop key="file.resource.loader.class">
                    org.apache.velocity.runtime.resource.loader.FileResourceLoader
                </prop>
                <prop key="file.resource.loader.path">/opt/shibboleth-idp/conf</prop>
                <prop key="file.resource.loader.cache">false</prop>
            </props>
        </property>
    </bean>

</beans>

Now you need to add your custom Spring configuration to the IdP's deployment descriptor:

web.xml
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>$IDP_HOME$/conf/internal.xml; $IDP_HOME$/conf/service.xml; $IDP_HOME$/conf/myext.xml</param-value>
    </context-param>

Note that the web.xml file contains a couple of additional JSP page definitions as custom respones for 404 and 500 errors. You may wish to comment those out and use web server-wide configuration for those errors to avoid additional in-warfile error pages.

The error template itself is a standard Velocity macro template but lives in the file system and will be reloaded on every use. (You can enable caching of the file by modifying the relevant property in the bean definition.) Like the original JSP option, objects are available for interrogation by the template:

  • $request
    • The HttpServletRequest being processed by the IdP when the error occurred.
  • $requestError
    • The exception object thrown by the IdP. This may be a wrapper around the actual error object, which will be available via $requestError.getCause()
  • $encoder
    • An ESAPI encoder instance for use in properly encoding anything inserted into the template. Always use a proper encoder method such as $encoder.encodeForHTML() when generating output.

In principle, you could add additional IdP objects into the error handling context, if you needed them, by modifying edu/internet2/middleware/shibboleth/common/profile/provider/VelocityErrorHandler.java in the java-shib-common project, or copying that to your own IdP extension as the basis of your own error handler.

An example error template follows. This is based on a template used at Ohio State to handle a variety of common errors with special display logic. The IdP currently does not provide a good way to test for specialized errors, so this example relies on the technique of direct comparison of error messages. Be warned that those messages could change across releases, even patches. They are not a public API.

 Example Error Template

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

#set ($stale = 0)
#if ($requestError.getCause())
  #if ($requestError.getCause().getMessage().contains("Message was rejected due to issue instant expiration") ||
       $requestError.getCause().getMessage().contains("Rejecting replayed message ID")
  )
    #set ($stale = 1)
  #end
#end

#if ($requestError.getMessage().equals("Error decoding Shibboleth SSO request") ||
     $requestError.getMessage().equals("Error decoding authentication request message"))
  #if (!$request.getQueryString() && !$request.getParameter("SAMLRequest"))
    #set ($stale = 1)
  #end
#end

  <head>
     <!-- Your stuff -->
  </head>

  <body>
    <!-- Your stuff -->

#if ($stale == 1)
      <!-- Error content for back button, bookmarks, etc.  -->
#else
  #if ($requestError.getMessage().contains("not configured for relying party"))
      <!-- Unauthorized SP -->
  #elseif ($requestError.getMessage().contains("No peer endpoint available to which to send SAML response"))
      <!-- Unregistered or invalid host endpont. -->
  #else
  <p>The Web Login Service experienced a technical failure.</p>

  <p>Please contact the IT Service Desk, indicate what you were trying to access, and include the following error information:</p>

   #if ($requestError)
     <p><strong>$encoder.encodeForHTML($requestError.getMessage())</strong></p>

     #if ($requestError.getCause() && $requestError.getCause().getMessage())
       <p><strong>$encoder.encodeForHTML($requestError.getCause().getMessage())</strong></p>
     #end

   #else
     <p><strong>An uncaught exception was thrown.</strong></p>
   #end

  #end
#end

  </body>
</html>