From 9858f6a7d7e1bd5e2f9983cbdf4f152aa6db73bc Mon Sep 17 00:00:00 2001
From: Vladimir Schafer
Date: Sat, 26 Mar 2011 11:12:38 +0200
Subject: [PATCH] SES-82 Adding support for ECP profile. Proxy count can now be
configured in the WebSSOProfileOptions. List of allowed IDPs can now be
configured in the WebSSOProfileOptions.
---
spring-security-saml/pom.xml | 3 +
spring-security-saml/saml2-core/pom.xml | 20 +++
.../security/saml/SAMLBootstrap.java | 22 ++-
.../security/saml/SAMLConstants.java | 3 +
.../security/saml/SAMLEntryPoint.java | 54 ++++++-
.../saml/metadata/MetadataGenerator.java | 8 +-
.../saml/processor/HTTPPAOS11Binding.java | 52 +++++++
.../saml/websso/AbstractProfileBase.java | 23 ++-
.../saml/websso/SingleLogoutProfileImpl.java | 4 +-
.../saml/websso/WebSSOProfileECPImpl.java | 146 ++++++++++++++++++
.../saml/websso/WebSSOProfileImpl.java | 109 ++++++++-----
.../saml/websso/WebSSOProfileOptions.java | 86 +++++++++--
.../security/saml/SAMLEntryPointTest.java | 20 ++-
.../saml/websso/WebSSOProfileImplTest.java | 6 +-
spring-security-saml/saml2-sample/pom.xml | 6 +
.../resources/security/securityContext.xml | 34 ++--
.../saml2-sample/src/main/webapp/index.jsp | 21 +++
17 files changed, 527 insertions(+), 90 deletions(-)
create mode 100755 spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/processor/HTTPPAOS11Binding.java
create mode 100755 spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileECPImpl.java
diff --git a/spring-security-saml/pom.xml b/spring-security-saml/pom.xml
index 2b796f0..7b00b82 100644
--- a/spring-security-saml/pom.xml
+++ b/spring-security-saml/pom.xml
@@ -53,6 +53,9 @@
Rob Moore
+
+ Jonathan Tellier
+
diff --git a/spring-security-saml/saml2-core/pom.xml b/spring-security-saml/saml2-core/pom.xml
index 56f60f5..51d33e2 100644
--- a/spring-security-saml/saml2-core/pom.xml
+++ b/spring-security-saml/saml2-core/pom.xml
@@ -76,6 +76,26 @@
4.4
test
+
+
+ xmlunit
+ xmlunit
+ 1.0
+ test
+
+
+
+ org.springframework
+ spring-mock
+ 2.0.8
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLBootstrap.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLBootstrap.java
index c3c9309..7328747 100755
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLBootstrap.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLBootstrap.java
@@ -1,6 +1,20 @@
+/* Copyright 2011 Vladimir Schaefer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.springframework.security.saml;
-import org.opensaml.DefaultBootstrap;
+import org.opensaml.PaosBootstrap;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.parse.ParserPool;
import org.springframework.beans.BeansException;
@@ -11,6 +25,8 @@
/**
* Initialization for SAML library. Is automatically called as part of Spring initialization.
+ *
+ * @author Vladimir Schaefer
*/
public class SAMLBootstrap implements BeanFactoryPostProcessor {
@@ -22,7 +38,7 @@ public class SAMLBootstrap implements BeanFactoryPostProcessor {
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
- DefaultBootstrap.bootstrap();
+ PaosBootstrap.bootstrap();
ParserPool pool = beanFactory.getBean(ParserPool.class);
new ParserPoolHolder(pool);
} catch (ConfigurationException e) {
@@ -30,4 +46,4 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
}
}
-}
\ No newline at end of file
+}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLConstants.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLConstants.java
index 3dc8b14..dc1fc35 100755
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLConstants.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLConstants.java
@@ -28,5 +28,8 @@ public class SAMLConstants {
public static final String SUCCESS = "SUCCESS";
public static final String FAILURE = "FAILURE";
+
+ public static final String PAOS_HTTP_ACCEPT_HEADER = "application/vnd.paos+xml";
+ public static final String PAOS_HTTP_HEADER = "PAOS";
}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLEntryPoint.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLEntryPoint.java
index ec36ad5..349405d 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLEntryPoint.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/SAMLEntryPoint.java
@@ -61,6 +61,7 @@ public class SAMLEntryPoint extends GenericFilterBean implements AuthenticationE
protected String idpSelectionPath;
protected WebSSOProfileOptions defaultOptions;
protected WebSSOProfile webSSOprofile;
+ protected WebSSOProfile webSSOprofileECP;
protected MetadataManager metadata;
protected SAMLLogger samlLogger;
protected SAMLContextProvider contextProvider;
@@ -130,14 +131,30 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A
try {
- if (idpSelectionPath != null && !isLoginRequest(request)) {
+ boolean ecpRequest = isECPRequest(request);
+
+ if (!ecpRequest && idpSelectionPath != null && !isLoginRequest(request)) {
+
request.getRequestDispatcher(idpSelectionPath).include(request, response);
+
} else {
+
SAMLMessageContext context = contextProvider.getLocalEntity(request, response);
SAMLMessageStorage storage = new HttpSessionStorage(request);
- WebSSOProfileOptions options = getProfileOptions(request, response, e);
- webSSOprofile.sendAuthenticationRequest(context, options, storage);
+ WebSSOProfileOptions options = getProfileOptions(request, response, context, e);
+
+ if (ecpRequest) {
+ if (webSSOprofileECP == null) {
+ throw new ServletException("ECP profile isn't available in the entry point, check your configuration");
+ } else {
+ webSSOprofileECP.sendAuthenticationRequest(context, options, storage);
+ }
+ } else {
+ webSSOprofile.sendAuthenticationRequest(context, options, storage);
+ }
+
samlLogger.log(SAMLConstants.AUTH_N_REQUEST, SAMLConstants.SUCCESS, context, e);
+
}
} catch (SAMLException e1) {
@@ -150,6 +167,24 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A
}
+ /**
+ * Analyzes the request headers in order to determine if it comes from an ECP-enabled
+ * client and based on this decides whether ECP profile will be used. Subclasses can override
+ * the method to control when is the ECP invoked.
+ *
+ * @param request request to analyze
+ * @return whether the request comes from an ECP-enabled client or not
+ */
+ protected boolean isECPRequest(HttpServletRequest request) {
+ String acceptHeader = request.getHeader("Accept");
+
+ return acceptHeader != null
+ && acceptHeader.contains(SAMLConstants.PAOS_HTTP_ACCEPT_HEADER)
+ && ("ver='" + org.opensaml.common.xml.SAMLConstants.PAOS_NS + "';'"
+ + org.opensaml.common.xml.SAMLConstants.SAML20ECP_NS + "'").equals(
+ request.getHeader(SAMLConstants.PAOS_HTTP_HEADER));
+ }
+
/**
* Method is supposed to populate preferences used to construct the SAML message. Method can be overridden to provide
* logic appropriate for given application. In case defaultOptions object was set it will be used as basis for construction
@@ -157,12 +192,13 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A
*
* @param request request
* @param response response
+ * @param context containing local entity
* @param exception exception causing invocation of this entry point (can be null)
* @return populated webSSOprofile
* @throws MetadataProviderException in case metadata loading fails
* @throws ServletException in case any other error occurs
*/
- protected WebSSOProfileOptions getProfileOptions(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws MetadataProviderException, ServletException {
+ protected WebSSOProfileOptions getProfileOptions(HttpServletRequest request, HttpServletResponse response, SAMLMessageContext context, AuthenticationException exception) throws MetadataProviderException, ServletException {
WebSSOProfileOptions ssoProfileOptions;
if (defaultOptions != null) {
@@ -270,6 +306,14 @@ public void setWebSSOprofile(WebSSOProfile webSSOprofile) {
this.webSSOprofile = webSSOprofile;
}
+ public WebSSOProfile getWebSSOprofileECP() {
+ return webSSOprofileECP;
+ }
+
+ public void setWebSSOprofileECP(WebSSOProfile webSSOprofileECP) {
+ this.webSSOprofileECP = webSSOprofileECP;
+ }
+
/**
* Logger for SAML events, cannot be null, must be set.
*
@@ -317,4 +361,4 @@ public void afterPropertiesSet() throws ServletException {
Assert.notNull(contextProvider, "Context provider must be set");
}
-}
\ No newline at end of file
+}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/metadata/MetadataGenerator.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/metadata/MetadataGenerator.java
index bc66e22..d8747da 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/metadata/MetadataGenerator.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/metadata/MetadataGenerator.java
@@ -67,8 +67,6 @@ public class MetadataGenerator implements ApplicationContextAware {
private boolean wantAssertionSigned = true;
private boolean signMetadata = true;
-
-
private String signingKey = null;
private String encryptionKey = null;
@@ -82,6 +80,7 @@ public class MetadataGenerator implements ApplicationContextAware {
NameIDType.X509_SUBJECT);
private static final Collection defaultBindings = Arrays.asList(SAMLConstants.SAML2_POST_BINDING_URI,
+ SAMLConstants.SAML2_PAOS_BINDING_URI,
SAMLConstants.SAML2_ARTIFACT_BINDING_URI,
SAMLConstants.SAML2_REDIRECT_BINDING_URI,
SAMLConstants.SAML2_SOAP11_BINDING_URI);
@@ -195,6 +194,11 @@ protected SPSSODescriptor buildSPSSODescriptor(String entityBaseURL, String enti
index++;
isDefault = false;
}
+ if (includedBindings.contains(SAMLConstants.SAML2_PAOS_BINDING_URI)) {
+ spDescriptor.getAssertionConsumerServices().add(getAssertionConsumerService(entityBaseURL, entityAlias, isDefault, index, SAMLConstants.SAML2_PAOS_BINDING_URI));
+ index++;
+ isDefault = false;
+ }
if (includedBindings.contains(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
spDescriptor.getSingleLogoutServices().add(getSingleLogoutService(entityBaseURL, entityAlias, SAMLConstants.SAML2_REDIRECT_BINDING_URI));
}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/processor/HTTPPAOS11Binding.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/processor/HTTPPAOS11Binding.java
new file mode 100755
index 0000000..b01c398
--- /dev/null
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/processor/HTTPPAOS11Binding.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 Jonathan Tellier
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.saml.processor;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.liberty.binding.decoding.HTTPPAOS11Decoder;
+import org.opensaml.liberty.binding.encoding.HTTPPAOS11Encoder;
+import org.opensaml.ws.transport.InTransport;
+import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
+import org.opensaml.xml.parse.ParserPool;
+import org.springframework.security.saml.processor.HTTPSOAP11Binding;
+
+public class HTTPPAOS11Binding extends HTTPSOAP11Binding {
+
+ public HTTPPAOS11Binding(ParserPool parserPool) {
+ super(new HTTPPAOS11Decoder(parserPool), new HTTPPAOS11Encoder());
+ }
+
+ @Override
+ public boolean supports(InTransport transport) {
+ if (transport instanceof HttpServletRequestAdapter) {
+ HttpServletRequestAdapter t = (HttpServletRequestAdapter) transport;
+ HttpServletRequest request = t.getWrappedRequest();
+ return "POST".equalsIgnoreCase(t.getHTTPMethod())
+ && request.getContentType().startsWith(
+ org.springframework.security.saml.SAMLConstants.PAOS_HTTP_ACCEPT_HEADER);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String getCommunicationProfileId() {
+ return SAMLConstants.SAML2_PAOS_BINDING_URI;
+ }
+
+}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/AbstractProfileBase.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/AbstractProfileBase.java
index 85c9d74..8a4d549 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/AbstractProfileBase.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/AbstractProfileBase.java
@@ -29,6 +29,7 @@
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.security.MetadataCriteria;
import org.opensaml.security.SAMLSignatureProfileValidator;
+import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.credential.UsageType;
@@ -144,6 +145,20 @@ protected SPSSODescriptor getSPDescriptor(String spId) throws MetadataProviderEx
return spDescriptor;
}
+ /**
+ * Method calls the processor and sends the message containted in the context. Subclasses can provide additional
+ * processing before the message delivery.
+ *
+ * @param context context
+ * @param sign whether the message should be signed
+ * @throws MetadataProviderException metadata error
+ * @throws SAMLException SAML encoding error
+ * @throws org.opensaml.ws.message.encoder.MessageEncodingException message encoding error
+ */
+ protected void sendMessage(SAMLMessageContext context, boolean sign) throws MetadataProviderException, SAMLException, MessageEncodingException {
+ processor.sendMessage(context, sign);
+ }
+
protected Status getStatus(String code, String statusMessage) {
SAMLObjectBuilder codeBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
StatusCode statusCode = codeBuilder.buildObject();
@@ -170,11 +185,17 @@ protected Status getStatus(String code, String statusMessage) {
* @param service service to use as destination for the request
*/
protected void buildCommonAttributes(RequestAbstractType request, Endpoint service) {
+
request.setID(generateID());
request.setIssuer(getIssuer());
request.setVersion(SAMLVersion.VERSION_20);
request.setIssueInstant(new DateTime());
- request.setDestination(service.getLocation());
+
+ if (service != null) {
+ // Service is now known when we do not know which IDP will be used
+ request.setDestination(service.getLocation());
+ }
+
}
protected Issuer getIssuer() {
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/SingleLogoutProfileImpl.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/SingleLogoutProfileImpl.java
index 4a50c28..6507305 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/SingleLogoutProfileImpl.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/SingleLogoutProfileImpl.java
@@ -77,7 +77,7 @@ public void sendLogoutRequest(SAMLMessageContext context, SAMLCredential credent
context.setPeerExtendedMetadata(idpExtendedMetadata);
boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutRequestSigned();
- processor.sendMessage(context, signMessage);
+ sendMessage(context, signMessage);
messageStorage.storeMessage(logoutRequest.getID(), logoutRequest);
}
@@ -270,7 +270,7 @@ protected void sendLogoutResponse(Status status, SAMLMessageContext context) thr
context.setPeerEntityRoleMetadata(idpDescriptor);
boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutResponseSigned();
- processor.sendMessage(context, signMessage);
+ sendMessage(context, signMessage);
}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileECPImpl.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileECPImpl.java
new file mode 100755
index 0000000..8d35f4a
--- /dev/null
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileECPImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 Jonathan Tellier, Vladimir Schaefer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.saml.websso;
+
+import org.opensaml.common.SAMLException;
+import org.opensaml.common.SAMLObjectBuilder;
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.saml2.ecp.RelayState;
+import org.opensaml.saml2.ecp.Request;
+import org.opensaml.saml2.metadata.AssertionConsumerService;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.ws.message.encoder.MessageEncodingException;
+import org.opensaml.ws.soap.common.SOAPObjectBuilder;
+import org.opensaml.ws.soap.soap11.Envelope;
+import org.opensaml.ws.soap.util.SOAPHelper;
+import org.springframework.security.saml.context.SAMLMessageContext;
+import org.springframework.security.saml.metadata.MetadataManager;
+import org.springframework.security.saml.processor.SAMLProcessor;
+import org.springframework.security.saml.storage.SAMLMessageStorage;
+import org.springframework.security.saml.util.SAMLUtil;
+
+import java.util.Set;
+
+/**
+ * Class implementing the SAML ECP Profile and offers capabilities for SP initialized SSO and
+ * process Response coming from IDP or IDP initialized SSO. PAOS Binding is supported
+ *
+ * @author Jonathan Tellier, Vladimir Schaefer
+ */
+public class WebSSOProfileECPImpl extends WebSSOProfileImpl {
+
+ public WebSSOProfileECPImpl() {
+ }
+
+ public WebSSOProfileECPImpl(SAMLProcessor processor, MetadataManager manager) {
+ super(processor, manager);
+ }
+
+ @Override
+ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileOptions options, SAMLMessageStorage messageStorage)
+ throws SAMLException, MetadataProviderException, MessageEncodingException {
+
+ SPSSODescriptor spDescriptor = getSPDescriptor(metadata.getHostedSPName());
+ AssertionConsumerService assertionConsumer = SAMLUtil.getAssertionConsumerForBinding(spDescriptor, SAMLConstants.SAML2_PAOS_BINDING_URI);
+
+ SOAPHelper.addHeaderBlock(context, getPAOSRequest(assertionConsumer));
+ SOAPHelper.addHeaderBlock(context, getECPRequest(options));
+
+ if (context.getRelayState() != null) {
+ SOAPHelper.addHeaderBlock(context, getRelayState(context.getRelayState()));
+ }
+
+ // The last parameter refers to the IdP that should receive the message. However,
+ // in ECP, we don't know in advance which IdP will be contacted.
+ AuthnRequest authRequest = getAuthnRequest(options, assertionConsumer, null);
+
+ context.setCommunicationProfileId(SAMLConstants.SAML2_PAOS_BINDING_URI);
+ context.setOutboundMessage(getEnvelope());
+ context.setOutboundSAMLMessage(authRequest);
+
+ sendMessage(context, spDescriptor.isAuthnRequestsSigned());
+ messageStorage.storeMessage(authRequest.getID(), authRequest);
+
+ }
+
+ protected org.opensaml.liberty.paos.Request getPAOSRequest(AssertionConsumerService assertionConsumer) {
+
+ SAMLObjectBuilder paosRequestBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(org.opensaml.liberty.paos.Request.DEFAULT_ELEMENT_NAME);
+ org.opensaml.liberty.paos.Request paosRequest = paosRequestBuilder.buildObject();
+
+ paosRequest.setSOAP11Actor(Request.SOAP11_ACTOR_NEXT);
+ paosRequest.setSOAP11MustUnderstand(true);
+ paosRequest.setResponseConsumerURL(assertionConsumer.getLocation());
+ paosRequest.setService(SAMLConstants.SAML20ECP_NS);
+
+ return paosRequest;
+
+ }
+
+ protected Request getECPRequest(WebSSOProfileOptions options) {
+
+ SAMLObjectBuilder ecpRequestBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Request.DEFAULT_ELEMENT_NAME);
+ Request ecpRequest = ecpRequestBuilder.buildObject();
+
+ ecpRequest.setSOAP11Actor(Request.SOAP11_ACTOR_NEXT);
+ ecpRequest.setSOAP11MustUnderstand(true);
+
+ ecpRequest.setPassive(options.getPassive());
+ ecpRequest.setProviderName(options.getProviderName());
+ ecpRequest.setIssuer(getIssuer());
+
+ Set idpEntityNames = options.getAllowedIDPs();
+ if (options.isIncludeScoping() && idpEntityNames != null) {
+ ecpRequest.setIDPList(buildIDPList(idpEntityNames, null));
+ }
+
+ return ecpRequest;
+
+ }
+
+ protected Envelope getEnvelope() {
+
+ SOAPObjectBuilder envelopeBuilder = (SOAPObjectBuilder) builderFactory.getBuilder(Envelope.DEFAULT_ELEMENT_NAME);
+ return envelopeBuilder.buildObject();
+
+ }
+
+ /**
+ * Method creates a relayState element usable with the ECP profile.
+ * @param relayStateValue value to include, mustn't be null
+ * @return relay state object
+ */
+ protected RelayState getRelayState(String relayStateValue) {
+
+ if (relayStateValue == null) {
+ throw new IllegalArgumentException("RelayStateValue can't be null");
+ }
+ if (relayStateValue.length() > 80) {
+ throw new IllegalArgumentException("Relay state can't exceed size 80 when using ECP profile");
+ }
+
+ SAMLObjectBuilder relayStateBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(RelayState.DEFAULT_ELEMENT_NAME);
+ RelayState relayState = relayStateBuilder.buildObject();
+ relayState.setSOAP11Actor(RelayState.SOAP11_ACTOR_NEXT);
+ relayState.setSOAP11MustUnderstand(true);
+ relayState.setValue(relayStateValue);
+ return relayState;
+
+ }
+
+}
\ No newline at end of file
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileImpl.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileImpl.java
index 2095a71..fd534e3 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileImpl.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileImpl.java
@@ -32,6 +32,7 @@
import org.springframework.security.saml.util.SAMLUtil;
import java.util.Collection;
+import java.util.Set;
/**
* Class implements WebSSO profile and offers capabilities for SP initialized SSO and
@@ -42,8 +43,6 @@
*/
public class WebSSOProfileImpl extends AbstractProfileBase implements WebSSOProfile {
- private static final int DEFAULT_PROXY_COUNT = 2;
-
public WebSSOProfileImpl() {
}
@@ -63,25 +62,27 @@ public WebSSOProfileImpl(SAMLProcessor processor, MetadataManager manager) {
*/
public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileOptions options, SAMLMessageStorage messageStorage) throws SAMLException, MetadataProviderException, MessageEncodingException {
+ // Verify we deal with a local SP
+ if (!SPSSODescriptor.DEFAULT_ELEMENT_NAME.equals(context.getLocalEntityRole())) {
+ throw new SAMLException("WebSSO can only be initialized for local SP, but localEntityRole is: " + context.getLocalEntityRole());
+ }
+
// Initialize IDP based on options or use default
String idpId = options.getIdp();
if (idpId == null) {
idpId = metadata.getDefaultIDP();
}
- // Verify we deal with a local SP
- if (!SPSSODescriptor.DEFAULT_ELEMENT_NAME.equals(context.getLocalEntityRole())) {
- throw new SAMLException("WebSSO can only be initialized for local SP, but localEntityRole is: " + context.getLocalEntityRole());
- }
-
+ // Load the entities
+ SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
IDPSSODescriptor idpssoDescriptor = getIDPDescriptor(idpId);
ExtendedMetadata idpExtendedMetadata = metadata.getExtendedMetadata(idpId);
- SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
-
String binding = SAMLUtil.getLoginBinding(options, idpssoDescriptor, spDescriptor);
- AssertionConsumerService assertionConsumerForBinding = SAMLUtil.getAssertionConsumerForBinding(spDescriptor, binding);
SingleSignOnService bindingService = SAMLUtil.getSSOServiceForBinding(idpssoDescriptor, binding);
- AuthnRequest authRequest = getAuthnRequest(options, idpId, assertionConsumerForBinding, bindingService);
+ boolean sign = spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned();
+
+ AssertionConsumerService assertionConsumerForBinding = SAMLUtil.getAssertionConsumerForBinding(spDescriptor, binding);
+ AuthnRequest authRequest = getAuthnRequest(options, assertionConsumerForBinding, bindingService);
// TODO optionally implement support for conditions, subject
@@ -93,7 +94,7 @@ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileO
context.setPeerEntityRoleMetadata(idpssoDescriptor);
context.setPeerExtendedMetadata(idpExtendedMetadata);
- processor.sendMessage(context, spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned());
+ sendMessage(context, sign);
messageStorage.storeMessage(authRequest.getID(), authRequest);
}
@@ -103,14 +104,16 @@ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileO
* idpEntityDescriptor, with an expected response to the assertionConsumer address.
*
* @param options preferences of message creation
- * @param idpEntityId entity ID of the IDP
* @param assertionConsumer assertion consumer where the IDP should respond
* @param bindingService service used to deliver the request
* @return authnRequest ready to be sent to IDP
* @throws SAMLException error creating the message
* @throws MetadataProviderException error retreiving metadata
*/
- protected AuthnRequest getAuthnRequest(WebSSOProfileOptions options, String idpEntityId, AssertionConsumerService assertionConsumer, SingleSignOnService bindingService) throws SAMLException, MetadataProviderException {
+ protected AuthnRequest getAuthnRequest(WebSSOProfileOptions options,
+ AssertionConsumerService assertionConsumer,
+ SingleSignOnService bindingService) throws SAMLException,
+ MetadataProviderException {
SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
AuthnRequest request = builder.buildObject();
@@ -118,19 +121,20 @@ protected AuthnRequest getAuthnRequest(WebSSOProfileOptions options, String idpE
request.setForceAuthn(options.getForceAuthN());
buildCommonAttributes(request, bindingService);
+
+ buildScoping(request, bindingService, options);
builNameIDPolicy(request, options);
buildAuthnContext(request, options);
- buildScoping(request, idpEntityId, bindingService, options);
buildReturnAddress(request, assertionConsumer);
- return request;
+ return request;
}
/**
* Fills the request with required AuthNContext according to selected options.
*
- * @param request request to fill
- * @param options options driving generation of the element
+ * @param request request to fill
+ * @param options options driving generation of the element
*/
protected void builNameIDPolicy(AuthnRequest request, WebSSOProfileOptions options) {
@@ -139,6 +143,8 @@ protected void builNameIDPolicy(AuthnRequest request, WebSSOProfileOptions optio
NameIDPolicy nameIDPolicy = builder.buildObject();
nameIDPolicy.setFormat(options.getNameID());
nameIDPolicy.setAllowCreate(options.isAllowCreate());
+
+ // TODO The SPNameQualifier seems invalid when interacting with a Shibboleth IdP
nameIDPolicy.setSPNameQualifier(getSPNameQualifier());
request.setNameIDPolicy(nameIDPolicy);
}
@@ -146,14 +152,14 @@ protected void builNameIDPolicy(AuthnRequest request, WebSSOProfileOptions optio
}
protected String getSPNameQualifier() {
- return metadata.getHostedSPName();
+ return metadata.getHostedSPName(); // TODO Fix
}
/**
* Fills the request with required AuthNContext according to selected options.
*
- * @param request request to fill
- * @param options options driving generation of the element
+ * @param request request to fill
+ * @param options options driving generation of the element
*/
protected void buildAuthnContext(AuthnRequest request, WebSSOProfileOptions options) {
@@ -165,7 +171,7 @@ protected void buildAuthnContext(AuthnRequest request, WebSSOProfileOptions opti
authnContext.setComparison(options.getAuthnContextComparison());
for (String context : contexts) {
-
+
SAMLObjectBuilder contextRefBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
AuthnContextClassRef authnContextClassRef = contextRefBuilder.buildObject();
authnContextClassRef.setAuthnContextClassRef(context);
@@ -196,36 +202,59 @@ private void buildReturnAddress(AuthnRequest request, AssertionConsumerService s
/**
* Fills the request with information about scoping, including IDP in the scope IDP List.
*
- * @param request request to fill
- * @param idpEntityId id of the idp entity
- * @param serviceURI destination to send the request to
- * @param options options driving generation of the element
+ * @param request request to fill
+ * @param serviceURI destination to send the request to
+ * @param options options driving generation of the element, contains list of allowed IDPs
*/
- protected void buildScoping(AuthnRequest request, String idpEntityId, SingleSignOnService serviceURI, WebSSOProfileOptions options) {
+ protected void buildScoping(AuthnRequest request, SingleSignOnService serviceURI, WebSSOProfileOptions options) {
if (options.isIncludeScoping()) {
- SAMLObjectBuilder idpEntryBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(IDPEntry.DEFAULT_ELEMENT_NAME);
- IDPEntry idpEntry = idpEntryBuilder.buildObject();
- idpEntry.setProviderID(idpEntityId);
- idpEntry.setLoc(serviceURI.getLocation());
-
- SAMLObjectBuilder idpListBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(IDPList.DEFAULT_ELEMENT_NAME);
- IDPList idpList = idpListBuilder.buildObject();
- idpList.getIDPEntrys().add(idpEntry);
-
+ Set idpEntityNames = options.getAllowedIDPs();
+ IDPList idpList = buildIDPList(idpEntityNames, serviceURI);
SAMLObjectBuilder scopingBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(Scoping.DEFAULT_ELEMENT_NAME);
Scoping scoping = scopingBuilder.buildObject();
scoping.setIDPList(idpList);
+ scoping.setProxyCount(options.getProxyCount());
+ request.setScoping(scoping);
- if (options.isAllowProxy()) {
- scoping.setProxyCount(DEFAULT_PROXY_COUNT);
- }
+ }
- request.setScoping(scoping);
+ }
+
+ /**
+ * Builds an IdP List out of the idpEntityNames
+ *
+ * @param idpEntityNames The IdPs Entity IDs to include in the IdP List, no list is created when null
+ * @param serviceURI The binding service for an IdP for a specific binding. Should be null
+ * if there is more than one IdP in the list or if the destination IdP is not known in
+ * advance.
+ * @return an IdP List or null when idpEntityNames is null
+ */
+ protected IDPList buildIDPList(Set idpEntityNames, SingleSignOnService serviceURI) {
+
+ if (idpEntityNames == null) {
+ return null;
+ }
+
+ SAMLObjectBuilder idpEntryBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(IDPEntry.DEFAULT_ELEMENT_NAME);
+ SAMLObjectBuilder idpListBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(IDPList.DEFAULT_ELEMENT_NAME);
+ IDPList idpList = idpListBuilder.buildObject();
+ for (String entityID : idpEntityNames) {
+ IDPEntry idpEntry = idpEntryBuilder.buildObject();
+ idpEntry.setProviderID(entityID);
+ idpList.getIDPEntrys().add(idpEntry);
+
+ // The service URI would be null if the SP does not know in advance
+ // to which IdP the request is sent to.
+ if (serviceURI != null) {
+ idpEntry.setLoc(serviceURI.getLocation());
+ }
}
+ return idpList;
+
}
}
diff --git a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileOptions.java b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileOptions.java
index 6d857ba..5e40a90 100644
--- a/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileOptions.java
+++ b/spring-security-saml/saml2-core/src/main/java/org/springframework/security/saml/websso/WebSSOProfileOptions.java
@@ -18,6 +18,7 @@
import java.io.Serializable;
import java.util.Collection;
+import java.util.Set;
/**
* JavaBean contains properties allowing customization of SAML request message sent to the IDP.
@@ -28,15 +29,17 @@ public class WebSSOProfileOptions implements Serializable, Cloneable {
private String idp;
private String binding;
+ private Set allowedIDPs;
+ private String providerName;
// Name ID policy
private String nameID;
private boolean allowCreate;
private boolean passive = false;
- private boolean forceAuthN = false;
+ private boolean forceAuthn = false;
private boolean includeScoping = true;
- private boolean allowProxy = true;
+ private Integer proxyCount = 2;
private Collection authnContexts;
private AuthnContextComparisonTypeEnumeration authnContextComparison = AuthnContextComparisonTypeEnumeration.EXACT;
@@ -83,16 +86,22 @@ public boolean getPassive() {
return passive;
}
+ /**
+ * Sets whether the IdP should refrain from interacting with the user during the authentication process. Boolean
+ * values will be marshalled to either "true" or "false".
+ *
+ * @param passive true if passive authentication is allowed, false otherwise
+ */
public void setPassive(Boolean passive) {
this.passive = passive;
}
public boolean getForceAuthN() {
- return forceAuthN;
+ return forceAuthn;
}
public void setForceAuthN(Boolean forceAuthN) {
- this.forceAuthN = forceAuthN;
+ this.forceAuthn = forceAuthN;
}
/**
@@ -109,17 +118,23 @@ public void setIncludeScoping(boolean includeScoping) {
}
/**
- * True is proxying should be allowed in requests sent to IDP as part of the generated Scoping element.
- * Property includeScoping must be enabled for this value to take any effect.
- *
- * @return true if proxying is allowed
+ * @return null to skip proxyCount, 0 to disable proxying, >0 to allow proxying
*/
- public boolean isAllowProxy() {
- return allowProxy;
+ public Integer getProxyCount() {
+ return proxyCount;
}
- public void setAllowProxy(boolean allowProxy) {
- this.allowProxy = allowProxy;
+ /**
+ * Determines value to be used in the proxyCount attribute of the scope in the AuthnRequest. In case value is null
+ * the proxyCount attribute is omitted. Use zero to disable proxying or value >0 to specify how many hops are allowed.
+ *
+ * Property includeScoping must be enabled for this value to take any effect.
+ *
+ *
+ * @param proxyCount null to skip proxyCount in the AuthnRequest, 0 to disable proxying, >0 to allow proxying
+ */
+ public void setProxyCount(Integer proxyCount) {
+ this.proxyCount = proxyCount;
}
public Collection getAuthnContexts() {
@@ -144,10 +159,20 @@ public WebSSOProfileOptions clone() {
}
}
+ /**
+ * NameID to used or null to omit NameIDPolicy from request.
+ *
+ * @return name ID
+ */
public String getNameID() {
return nameID;
}
+ /**
+ * When set determines which NameIDPolicy will be requested as part of the AuthnRequest sent to the IDP.
+ *
+ * @param nameID name ID
+ */
public void setNameID(String nameID) {
this.nameID = nameID;
}
@@ -177,5 +202,40 @@ public void setAuthnContextComparison(AuthnContextComparisonTypeEnumeration auth
this.authnContextComparison = authnContextComparison;
}
}
-
+
+ public Set getAllowedIDPs() {
+ return allowedIDPs;
+ }
+
+ /**
+ * List of IDPs which are allowed to process the created AuthnRequest. IDP the request will be sent to is added
+ * automatically. In case value is null the allowedIDPs will not be included in the Scoping element.
+ *
+ * Property includeScoping must be enabled for this value to take any effect.
+ *
+ *
+ * @param allowedIDPs IDPs enabled to process the created authnRequest, null to skip the attribute from scoptin
+ */
+ public void setAllowedIDPs(Set allowedIDPs) {
+ this.allowedIDPs = allowedIDPs;
+ }
+
+ /**
+ * Human readable name of the local entity.
+ *
+ * @return entity name
+ */
+ public String getProviderName() {
+ return providerName;
+ }
+
+ /**
+ * Sets human readable name of the local entity used in ECP profile.
+ *
+ * @param providerName provider name
+ */
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
}
diff --git a/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/SAMLEntryPointTest.java b/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/SAMLEntryPointTest.java
index 700ee39..95d2b26 100644
--- a/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/SAMLEntryPointTest.java
+++ b/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/SAMLEntryPointTest.java
@@ -127,6 +127,8 @@ public void testIDPSelection() throws Exception {
entryPoint.setIdpSelectionPath("/selectIDP");
expect(request.getParameter(SAMLEntryPoint.LOGIN_PARAMETER)).andReturn("false");
expect(request.getRequestDispatcher("/selectIDP")).andReturn(dispatcher);
+ expect(request.getHeader("Accept")).andReturn(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
dispatcher.include(request, response);
replay(dispatcher);
@@ -144,9 +146,9 @@ public void testIDPSelection() throws Exception {
@Test
public void testInitialProfileOptions() throws Exception {
- WebSSOProfileOptions ssoProfileOptions = entryPoint.getProfileOptions(request, response, null);
+ WebSSOProfileOptions ssoProfileOptions = entryPoint.getProfileOptions(request, response, null, null);
assertEquals("http://localhost:8080/opensso", ssoProfileOptions.getIdp());
- assertTrue(ssoProfileOptions.isAllowProxy());
+ assertEquals(new Integer(2), ssoProfileOptions.getProxyCount());
assertTrue(ssoProfileOptions.isIncludeScoping());
assertFalse(ssoProfileOptions.getForceAuthN());
assertFalse(ssoProfileOptions.getPassive());
@@ -167,7 +169,7 @@ public void testDefaultProfileOptions() throws Exception {
WebSSOProfileOptions defaultOptions = new WebSSOProfileOptions();
defaultOptions.setIdp("ignoredValue");
- defaultOptions.setAllowProxy(false);
+ defaultOptions.setProxyCount(0);
defaultOptions.setIncludeScoping(false);
defaultOptions.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
@@ -175,9 +177,9 @@ public void testDefaultProfileOptions() throws Exception {
entryPoint.setDefaultProfileOptions(defaultOptions);
// Check that default values are used
- WebSSOProfileOptions ssoProfileOptions = entryPoint.getProfileOptions(request, response, null);
+ WebSSOProfileOptions ssoProfileOptions = entryPoint.getProfileOptions(request, response, null, null);
assertEquals("http://localhost:8080/opensso", ssoProfileOptions.getIdp());
- assertFalse(ssoProfileOptions.isAllowProxy());
+ assertEquals(new Integer(0), ssoProfileOptions.getProxyCount());
assertFalse(ssoProfileOptions.isIncludeScoping());
assertFalse(ssoProfileOptions.getForceAuthN());
assertFalse(ssoProfileOptions.getPassive());
@@ -185,13 +187,13 @@ public void testDefaultProfileOptions() throws Exception {
// Check that value can't be altered after being set
defaultOptions.setIncludeScoping(true);
- ssoProfileOptions = entryPoint.getProfileOptions(request, response, null);
+ ssoProfileOptions = entryPoint.getProfileOptions(request, response, null, null);
assertEquals("http://localhost:8080/opensso", ssoProfileOptions.getIdp());
assertFalse(ssoProfileOptions.isIncludeScoping());
// Check that default values can be cleared
entryPoint.setDefaultProfileOptions(null);
- ssoProfileOptions = entryPoint.getProfileOptions(request, response, null);
+ ssoProfileOptions = entryPoint.getProfileOptions(request, response, null, null);
assertEquals("http://localhost:8080/opensso", ssoProfileOptions.getIdp());
assertTrue(ssoProfileOptions.isIncludeScoping());
@@ -214,6 +216,8 @@ public void testInvalidIDP() throws Exception {
expect(session.getAttribute("_springSamlStorageKey")).andReturn(null);
session.setAttribute(eq("_springSamlStorageKey"), notNull());
expect(request.getParameter("idp")).andReturn("testIDP");
+ expect(request.getHeader("Accept")).andReturn(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
replayMock();
entryPoint.commence(request, response, null);
@@ -235,6 +239,8 @@ public void testCorrectIDP() throws Exception {
expect(session.getAttribute("_springSamlStorageKey")).andReturn(null);
session.setAttribute(eq("_springSamlStorageKey"), notNull());
expect(request.getParameter("idp")).andReturn("http://localhost:8080/opensso");
+ expect(request.getHeader("Accept")).andReturn(
+ "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
ssoProfile.sendAuthenticationRequest((SAMLMessageContext) notNull(), (WebSSOProfileOptions) notNull(), (SAMLMessageStorage) notNull());
replayMock();
diff --git a/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/websso/WebSSOProfileImplTest.java b/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/websso/WebSSOProfileImplTest.java
index 1f0efd4..25ad043 100644
--- a/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/websso/WebSSOProfileImplTest.java
+++ b/spring-security-saml/saml2-core/src/test/java/org/springframework/security/saml/websso/WebSSOProfileImplTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2009 Vladimir Sch�fer
+/* Copyright 2009 Vladimir Schafer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@
import static org.junit.Assert.*;
/**
- * @author Vladimir Sch�fer
+ * @author Vladimir Schafer
*/
public class WebSSOProfileImplTest extends SAMLTestBase {
@@ -294,7 +294,7 @@ public void testForce() throws Exception {
*/
@Test
public void testDisallowProxy() throws Exception {
- options.setAllowProxy(false);
+ options.setProxyCount(null);
storage.storeMessage((String) notNull(), (XMLObject) notNull());
replyMock();
profile.sendAuthenticationRequest(samlContext, options, storage);
diff --git a/spring-security-saml/saml2-sample/pom.xml b/spring-security-saml/saml2-sample/pom.xml
index 7e33105..eada829 100644
--- a/spring-security-saml/saml2-sample/pom.xml
+++ b/spring-security-saml/saml2-sample/pom.xml
@@ -55,6 +55,12 @@
2.0
provided
+
+
+ javax.servlet
+ jstl
+ 1.2
+
junit
diff --git a/spring-security-saml/saml2-sample/src/main/resources/security/securityContext.xml b/spring-security-saml/saml2-sample/src/main/resources/security/securityContext.xml
index 3e7c48a..97c7a05 100644
--- a/spring-security-saml/saml2-sample/src/main/resources/security/securityContext.xml
+++ b/spring-security-saml/saml2-sample/src/main/resources/security/securityContext.xml
@@ -28,7 +28,6 @@
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
-
@@ -117,7 +116,7 @@
-
+
http://idp.ssocircle.com/idp-meta.xml
@@ -133,7 +132,7 @@
-
+
@@ -141,29 +140,28 @@
-->
-
+
-
-
+
-
+
-
+
@@ -179,19 +177,23 @@
+
-
+
-
-
+
+
+
+
+
-
+
@@ -223,9 +225,13 @@
+
+
+
+
-
+
diff --git a/spring-security-saml/saml2-sample/src/main/webapp/index.jsp b/spring-security-saml/saml2-sample/src/main/webapp/index.jsp
index 358670d..313bcce 100644
--- a/spring-security-saml/saml2-sample/src/main/webapp/index.jsp
+++ b/spring-security-saml/saml2-sample/src/main/webapp/index.jsp
@@ -33,6 +33,27 @@
+
+
+
+ Principal's Attributes |
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+