Update August 2014: This plugin is deprecated. Instead, use the recently released spring-security-saml plugin as developed by SpringSource / Pivotal. It is superior in every way. Most likely, your app will require only basic integration with SAML. Consider proxying your app with Apache Httpd and using its popular Shibboleth module to take care of all the SAML stuff for you. If you need help figuring out which choice is right for you, read this Stackoverflow post.
This codebase will not be maintained any further. Do not expect any more releases, not even bugfixes. Migrate your existing projects.
This is an Spring Security module for authentication and authorization with Security Assertion Markup Language. It uses the OpenSAML library from Internet2.
This documentation contains references to identity providers in a closed development environment. Replace them with the URLs and certificates of your own identity provider.
Add the following dependency to your project (check this project for the latest version)
<dependency>
<groupId>org.surfnet.coin</groupId>
<artifactId>spring-security-opensaml</artifactId>
<version>${spring-security-opensaml.version}</version>
</dependency>When a user logs in, your application can create or update its existing profile.
Create a class that implements the nl.surfnet.spring.security.opensaml.Provisioner interface.
Next, create a bean of this class in your Spring context.
<bean id="samlProvisioner" class="org.example.project.provisioning.SAMLProvisioner"/><bean id="samlCertificateStore" class="nl.surfnet.spring.security.opensaml.CertificateStoreImpl">
<property name="certificates">
<map>
<entry key="https://engine.dev.surfconext.nl/authentication/idp/metadata" value="MIID/jCCAuYCCQCs7BsDR2N8tjANBgkqhkiG9w0BAQUFADCBwDELMAkGA1UEBhMCTkwxEDAOBgNVBAgTB1V0cmVjaHQxEDAOBgNVBAcTB1V0cmVjaHQxJTAjBgNVBAoTHFNVUkZuZXQgQlYgLSBUaGUgTmV0aGVybGFuZHMxHzAdBgNVBAsTFkNvbGxhYm9yYXRpb24gU2VydmljZXMxGDAWBgNVBAMTD1NVUkZjb25leHQtdGVzdDErMCkGCSqGSIb3DQEJARYcc3VyZmNvbmV4dC1iZWhlZXJAc3VyZm5ldC5ubDAeFw0xMTA2MjcxNTM0NDFaFw0yMTA2MjQxNTM0NDFaMIHAMQswCQYDVQQGEwJOTDEQMA4GA1UECBMHVXRyZWNodDEQMA4GA1UEBxMHVXRyZWNodDElMCMGA1UEChMcU1VSRm5ldCBCViAtIFRoZSBOZXRoZXJsYW5kczEfMB0GA1UECxMWQ29sbGFib3JhdGlvbiBTZXJ2aWNlczEYMBYGA1UEAxMPU1VSRmNvbmV4dC10ZXN0MSswKQYJKoZIhvcNAQkBFhxzdXJmY29uZXh0LWJlaGVlckBzdXJmbmV0Lm5sMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA27T+ALNpoU9PAvRYj2orOXaEVcy1fHSU/rZEakpgNzOFIEL9UB6BvtWdRbO5FT84yN+x2Qzpu6WLpwU7JFb36aRwPmBmabxUx95DhNQNFGA3ZkHu6DOA81GiG0Ll9p9S/EV2fmHGJdJjh5BP1v0/y7bJ/2JmvdzH+cFhEhFk0Ex9HNbC0Hmy9Sn8EXbg3RQO5/2e51wSv4uGALkyGM6lrOG/R1GenoAI8Ys7LNxj3NGkhKRUtpwHoIViWU5cOy26kG9VOG9bAVk51l0LNayMqyieX9UrCp1akQuP3Ir/ogtbKo2zg63Ho1cc43tHHdLZTHp2TWRRGEgnskvGZLddzwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQB8eUq/0ArBGnPZHNn2o2ip+3i0U4r0swKXjuYL/o2VXqo3eI9j/bCvWw5NOLQVbk/Whc6dSeYIt1oVW/ND4iQZ7LHZr9814IOE3KLGIMeZgjPsXH15o9W4aLC0npPYilw96dfIAq49tOn44jhsRHrdR8z/NFPXE09ehAEb7fOIrxdlf7YDGYx+gXLEnsJo75E6LwCr/y/MBd13DDJNc1HUViiEz+Mkfo4FEKe/5HgEvDy2XjuE1juDCqJ/07pBPHBd0DtM7uaxGw+Zt/Fc4uE0NvtlCZqShFUvMmqHL5oENOlfTmBSWJMbBAGs2O/JQirG2aYcULqXYoCGIPUF49Z6"/>
<entry key="https://mujina-idp.dev.surfconext.nl/" value="MIICHzCCAYgCCQD7KMJ17XQa7TANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJOTDEQMA4GA1UECAwHVXRyZWNodDEQMA4GA1UEBwwHVXRyZWNodDEQMA4GA1UECgwHU3VyZm5ldDEPMA0GA1UECwwGQ29uZXh0MB4XDTEyMDMwODA4NTQyNFoXDTEzMDMwODA4NTQyNFowVDELMAkGA1UEBhMCTkwxEDAOBgNVBAgMB1V0cmVjaHQxEDAOBgNVBAcMB1V0cmVjaHQxEDAOBgNVBAoMB1N1cmZuZXQxDzANBgNVBAsMBkNvbmV4dDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2slVe459WUDL4RXxJf5h5t5oUbPkPlFZ9lQysSoS3fnFTdCgzA6FzQzGRDcfRj0HnWBdA1YH+LxBjNcBIJ/nBc7Ssu4e4rMO3MSAV5Ouo3MaGgHqVq6dCD47f52b98df6QTAA3C+7sHqOdiQ0UDCAK0C+qP5LtTcmB8QrJhKmV8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQCvPhO0aSbqX7g7IkR79IFVdJ/P7uSlYFtJ9cMxec85cYLmWL1aVgF5ZFFJqC25blyPJu2GRcSxoVwB3ae8sPCECWwqRQA4AHKIjiW5NgrAGYR++ssTOQR8mcAucEBfNaNdlJoy8GdZIhHZNkGlyHfY8kWS3OWkGzhWSsuRCLl78A=="/>
</map>
</property>
</bean><opensaml:service-provider id="samlAuthenticationProvider"
message-handler-ref="samlMessageHandler"
preauth-filter-ref="samlPreAuthFilter"
entity-id="${ISSUING_ENTITY_ID}"
assertion-consumer-uri="${ASSERTION_CONSUMER_URI}"
provisioner-ref="samlProvisioner"
certificatestore-ref="samlCertificateStore"
authentication-manager-ref="authenticationManager" />Within <security:http> add the following configuration
<security:custom-filter position="PRE_AUTH_FILTER" ref="samlPreAuthFilter" />
<security:intercept-url pattern="/OpenSAML.sso/Login" access="permitAll"/>
<security:intercept-url pattern="${ASSERTION_CONSUMER_URI}" access="hasAnyRole(ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER)"/><security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="samlAuthenticationProvider"/>
</security:authentication-manager><bean id="authnRequestController" class="nl.surfnet.spring.security.opensaml.controller.AuthnRequestController">
<property name="SAMLMessageHandler" ref="samlMessageHandler" />
<property name="assertionConsumerServiceURL" value="${ASSERTION_CONSUMER_URL}" />
<property name="entityID" value="${ISSUING_ENTITY_ID}"/>
<property name="credentialResolver" ref="samlKeyStoreCredentialResolverDelegate" />
<property name="metaDataProperties" value ="metadata.test.properties"/>
</bean>The last property is the name of the properties file on the classpath to override the default metadata. An example:
service-name-en=SelfService
service-name-nl=SelfService
service-description-en=SelfService application
service-description-nl=SelfService applicatie
contact-person-administrative-given-name=SURFConext
contact-person-administrative-sur-name=SURFConext
contact-person-administrative-email=beheer@surfconext.nl
contact-person-technical-given-name=SURFConext
contact-person-technical-sur-name=SURFConext
contact-person-technical-email=beheer@surfconext.nl
contact-person-support-given-name=SURFConext
contact-person-support-sur-name=SURFConext
contact-person-support-email=beheer@surfconext.nl
The actual matadata is available on the endpoint: "http://hostname/applicationroot/OpenSAML.sso/Metadata"
Add a properties file for the Spring Security OpenSAML configuration (in this example opensaml.properties):
ISSUING_ENTITY_ID=http://local-myapp
WEB_APPLICATION_CHANNEL=http
WEB_APPLICATION_HOST_AND_PORT=localhost:8080
WEB_APPLICATION_CONTEXT_PATH=/myapp
ASSERTION_CONSUMER_URI=/AssertionConsumerService
ASSERTION_CONSUMER_URL=${WEB_APPLICATION_CHANNEL}://${WEB_APPLICATION_HOST_AND_PORT}${WEB_APPLICATION_CONTEXT_PATH}${ASSERTION_CONSUMER_URI}
MAX_PARSER_POOL_SIZE=2
REPLAY_CACHE_LIFE_IN_MILLIS=14400000
ISSUE_INSTANT_CHECK_CLOCK_SKEW_IN_SECONDS=90
ISSUE_INSTANT_CHECK_VALIDITY_TIME_IN_SECONDS=300
Load this property file from your application context:
<bean id="propertyResolver" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:opensaml.properties"/>
</bean>Create an opensaml-dispatcher-servlet.xml file inside the WEB-INF folder with the following contents:
<bean id="openSAMLDispatcherProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:opensaml.properties</value>
</list>
</property>
</bean>
<mvc:annotation-driven/>
<bean id="authnRequestController" class="nl.surfnet.spring.security.opensaml.controller.AuthnRequestController">
<property name="assertionConsumerServiceURL" value="${ASSERTION_CONSUMER_URL}" />
<property name="SAMLMessageHandler" ref="samlMessageHandler" />
<property name="entityID" value="${ISSUING_ENTITY_ID}"/>
</bean>Add a filter-mapping for the Spring Security filter chain and the servlet-mapping for the OpenSAML.sso/Login url:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>opensaml-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>opensaml-dispatcher</servlet-name>
<url-pattern>/OpenSAML.sso/Login</url-pattern>
</servlet-mapping>When your application requires a user is logged in you can redirect it to the AuthN request controller.
For a single identity provider add the following rule in the security filter chain:
<security:form-login login-page="/OpenSAML.sso/Login?target=https://engine.dev.surfconext.nl/authentication/idp/single-sign-on" default-target-url="/" />When you connect with multiple identity providers, you can show a Where Are You From (WAYF) page.
<security:intercept-url pattern="/myloginpage.jsp" access="permitAll"/>
<security:form-login login-page="/myloginpage.jsp" default-target-url="/" />Example of myloginpage.jsp:
<a href="OpenSAML.sso/Login?target=https://engine.dev.surfconext.nl/authentication/idp/single-sign-on">
Login at surfconext
</a>
<a href="OpenSAML.sso/Login?target=https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php">
Login at feido.no
</a>
<a href="OpenSAML.sso/Login?target=https://mujina-idp.dev.surfconext.nl/SingleSignOnService">
Login at Mujina
</a>