You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2014/07/16 13:23:49 UTC

git commit: Adding more SAML SSO work

Repository: cxf-fediz
Updated Branches:
  refs/heads/master 489659968 -> d6362d094


Adding more SAML SSO work


Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/d6362d09
Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/d6362d09
Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/d6362d09

Branch: refs/heads/master
Commit: d6362d0947d9bc20ca900efa8f278f676a931ae7
Parents: 4896599
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Wed Jul 16 12:23:23 2014 +0100
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Wed Jul 16 12:23:23 2014 +0100

----------------------------------------------------------------------
 .../cxf/fediz/core/config/FedizContext.java     |   4 +
 .../cxf/fediz/core/config/SAMLProtocol.java     |  52 +-
 .../core/processor/FedizProcessorFactory.java   |   6 +-
 .../cxf/fediz/core/processor/FedizRequest.java  |   7 +
 .../fediz/core/processor/SAMLProcessorImpl.java | 598 +++++++++++++++++++
 .../core/samlsso/EHCacheSPStateManager.java     | 176 ++++++
 .../cxf/fediz/core/samlsso/ResponseState.java   |  81 +++
 .../samlsso/SAMLProtocolResponseValidator.java  | 255 ++++++++
 .../core/samlsso/SAMLSSOResponseValidator.java  | 330 ++++++++++
 .../cxf/fediz/core/samlsso/SPStateManager.java  |  44 ++
 .../core/samlsso/SSOValidatorResponse.java      |  54 ++
 .../src/main/resources/schemas/FedizConfig.xsd  |   3 +
 .../fediz/jetty/FederationAuthenticator.java    |   1 +
 .../web/FederationAuthenticationFilter.java     |   1 +
 .../web/FederationAuthenticationFilter.java     |   1 +
 .../fediz/tomcat/FederationAuthenticator.java   |   2 +-
 .../fediz/integrationtests/HTTPTestUtils.java   |  67 +++
 17 files changed, 1672 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
index 2eae858..dc8b1ed 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
@@ -270,6 +270,10 @@ public class FedizContext implements Closeable {
         if (replayCache != null) {
             replayCache.close();
         }
+        if (protocol instanceof SAMLProtocol
+            && ((SAMLProtocol)protocol).getStateManager() != null) {
+            ((SAMLProtocol)protocol).getStateManager().close();
+        }
     }
     
     private Properties createCryptoProperties(TrustManagersType tm) {

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
index a137a6f..a1dee0b 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
@@ -19,10 +19,18 @@
 
 package org.apache.cxf.fediz.core.config;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cxf.fediz.core.TokenValidator;
 import org.apache.cxf.fediz.core.config.jaxb.ProtocolType;
 import org.apache.cxf.fediz.core.config.jaxb.SamlProtocolType;
+import org.apache.cxf.fediz.core.saml.SAMLTokenValidator;
 import org.apache.cxf.fediz.core.samlsso.AuthnRequestBuilder;
 import org.apache.cxf.fediz.core.samlsso.DefaultAuthnRequestBuilder;
+import org.apache.cxf.fediz.core.samlsso.EHCacheSPStateManager;
+import org.apache.cxf.fediz.core.samlsso.SPStateManager;
+import org.apache.cxf.fediz.core.util.ClassLoaderUtils;
 import org.apache.wss4j.common.util.Loader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,13 +40,15 @@ public class SAMLProtocol extends Protocol {
     private static final Logger LOG = LoggerFactory.getLogger(SAMLProtocol.class);
     
     private AuthnRequestBuilder authnRequestBuilder;
+    private SPStateManager stateManager;
+    private List<TokenValidator> validators = new ArrayList<TokenValidator>();
     
     public SAMLProtocol(ProtocolType protocolType) {
         super(protocolType);
         
-        /*FederationProtocolType fp = (FederationProtocolType)protocolType;
-        if (fp.getTokenValidators() != null && fp.getTokenValidators().getValidator() != null) {
-            for (String validatorClassname : fp.getTokenValidators().getValidator()) {
+        SamlProtocolType sp = (SamlProtocolType)protocolType;
+        if (sp.getTokenValidators() != null && sp.getTokenValidators().getValidator() != null) {
+            for (String validatorClassname : sp.getTokenValidators().getValidator()) {
                 Object obj = null;
                 try {
                     if (super.getClassloader() == null) {
@@ -56,13 +66,13 @@ public class SAMLProtocol extends Protocol {
                     LOG.error("Invalid TokenValidator implementation class: '" + validatorClassname + "'");
                 }
             }
-        }*/
+        }
         
         // add SAMLTokenValidator as the last one
         // Fediz chooses the first validator in the list if its
         // canHandleToken or canHandleTokenType method return true
-        //SAMLTokenValidator validator = new SAMLTokenValidator();
-        //validators.add(validators.size(), validator);
+        SAMLTokenValidator validator = new SAMLTokenValidator();
+        validators.add(validators.size(), validator);
     }
     
     protected SamlProtocolType getSAMLProtocol() {
@@ -88,6 +98,32 @@ public class SAMLProtocol extends Protocol {
     public void setWebAppDomain(String webAppDomain) {
         getSAMLProtocol().setWebAppDomain(webAppDomain);
     }
+    
+    public SPStateManager getStateManager() {
+        if (stateManager != null) {
+            return stateManager;
+        }
+        String stateManagerStr = getSAMLProtocol().getStateManager();
+        if (stateManagerStr == null || "".equals(stateManagerStr)) {
+            stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+        } else {
+            try {
+                Class<?> stateManagerClass = Loader.loadClass(stateManagerStr);
+                stateManager = (SPStateManager) stateManagerClass.newInstance();
+            } catch (ClassNotFoundException e) {
+                stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+            } catch (InstantiationException e) {
+                stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+            } catch (IllegalAccessException e) {
+                stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+            }
+        }
+        return stateManager;
+    }
+    
+    public void setStateManager(SPStateManager stateManager) {
+        this.stateManager = stateManager;
+    }
 
     public long getStateTimeToLive() {
         long ttl = getSAMLProtocol().getStateTimeToLive();
@@ -130,6 +166,10 @@ public class SAMLProtocol extends Protocol {
     public void setAuthnRequestBuilder(AuthnRequestBuilder authnRequestBuilder) {
         this.authnRequestBuilder = authnRequestBuilder;
     }
+    
+    public List<TokenValidator> getTokenValidators() {
+        return validators;
+    }
 
     
 }

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
index c44a40b..ebc441e 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
@@ -21,7 +21,7 @@ package org.apache.cxf.fediz.core.processor;
 
 import org.apache.cxf.fediz.core.config.FederationProtocol;
 import org.apache.cxf.fediz.core.config.Protocol;
-// import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
 
 /**
  * A Factory to return FedizProcessor instances depending on the Protocol
@@ -35,9 +35,9 @@ public final class FedizProcessorFactory {
     public static FedizProcessor newFedizProcessor(Protocol protocol) {
         if (protocol instanceof FederationProtocol) {
             return new FederationProcessorImpl();
-        } /*else if (protocol instanceof SAMLProtocol) {
+        } else if (protocol instanceof SAMLProtocol) {
             return new SAMLProcessorImpl();
-        }*/
+        }
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
index 68c8675..388cf36 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
@@ -29,6 +29,7 @@ public class FedizRequest implements Serializable {
     private String action;
     private String responseToken;
     private String freshness;
+    private String state;
     private Certificate[] certs;
 
     public Certificate[] getCerts() {
@@ -55,6 +56,12 @@ public class FedizRequest implements Serializable {
     public void setFreshness(String freshness) {
         this.freshness = freshness;
     }
+    public String getState() {
+        return state;
+    }
+    public void setState(String state) {
+        this.state = state;
+    }
 
 
 }

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
new file mode 100644
index 0000000..918452a
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
@@ -0,0 +1,598 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.processor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.zip.DataFormatException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.fediz.core.FederationConstants;
+import org.apache.cxf.fediz.core.TokenValidator;
+import org.apache.cxf.fediz.core.TokenValidatorRequest;
+import org.apache.cxf.fediz.core.TokenValidatorResponse;
+import org.apache.cxf.fediz.core.config.FederationProtocol;
+import org.apache.cxf.fediz.core.config.FedizContext;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.exception.ProcessingException;
+import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
+import org.apache.cxf.fediz.core.metadata.MetadataWriter;
+import org.apache.cxf.fediz.core.samlsso.AuthnRequestBuilder;
+import org.apache.cxf.fediz.core.samlsso.CompressionUtils;
+import org.apache.cxf.fediz.core.samlsso.RequestState;
+import org.apache.cxf.fediz.core.samlsso.SAMLProtocolResponseValidator;
+import org.apache.cxf.fediz.core.spi.IDPCallback;
+import org.apache.cxf.fediz.core.util.DOMUtils;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.OpenSAMLUtil;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.apache.xml.security.exceptions.Base64DecodingException;
+import org.apache.xml.security.utils.Base64;
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.xml.XMLObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SAMLProcessorImpl implements FedizProcessor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SAMLProcessorImpl.class);
+
+    /**
+     * Default constructor
+     */
+    public SAMLProcessorImpl() {
+        super();
+    }
+
+    @Override
+    public FedizResponse processRequest(FedizRequest request,
+                                             FedizContext config)
+        throws ProcessingException {
+        
+        if (!(config.getProtocol() instanceof SAMLProtocol)) {
+            LOG.error("Unsupported protocol");
+            throw new IllegalStateException("Unsupported protocol");
+        }
+        
+        if (request.getResponseToken() == null || request.getState() == null) {
+            LOG.error("Missing response token or RelayState parameters");
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        
+        return processSignInRequest(request, config);
+    }
+    
+
+    public Document getMetaData(FedizContext config) throws ProcessingException {
+        return new MetadataWriter().getMetaData(config);
+    }
+    /*
+    private RequestState processRelayState(String relayState, SAMLProtocol samlProtocol) 
+        throws ProcessingException {
+        if (relayState.getBytes().length < 0 || relayState.getBytes().length > 80) {
+            LOG.error("Invalid RelayState");
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        RequestState requestState = samlProtocol.getStateManager().removeRequestState(relayState);
+        if (requestState == null) {
+            LOG.error("Missing Request State");
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        if (isStateExpired(requestState.getCreatedAt(), 0, samlProtocol.getStateTimeToLive())) {
+            LOG.error("EXPIRED_REQUEST_STATE");
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        return requestState;
+    }
+    
+    private boolean isStateExpired(long stateCreatedAt, long expiresAt, long stateTTL) {
+        Date currentTime = new Date();
+        if (currentTime.after(new Date(stateCreatedAt + stateTTL))) {
+            return true;
+        }
+        
+        if (expiresAt > 0 && currentTime.after(new Date(expiresAt))) {
+            return true;
+        }
+        
+        return false;
+    }
+    */
+    protected FedizResponse processSignInRequest(
+            FedizRequest request, FedizContext config)
+        throws ProcessingException {
+        SAMLProtocol protocol = (SAMLProtocol)config.getProtocol();
+        // TODO RequestState requestState = processRelayState(request.getState(), protocol);
+        
+        InputStream tokenStream = null;
+        try {
+            byte[] deflatedToken = Base64.decode(request.getResponseToken());
+            tokenStream = CompressionUtils.inflate(deflatedToken); 
+        } catch (DataFormatException ex) {
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        } catch (Base64DecodingException e) {
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+
+        Document doc = null;
+        Element el = null;
+        try {
+            doc = DOMUtils.readXml(tokenStream);
+            el = doc.getDocumentElement();
+
+        } catch (Exception e) {
+            LOG.warn("Failed to parse token: " + e.getMessage());
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        
+        LOG.debug("Received response: " + DOM2Writer.nodeToString(el));
+        
+        XMLObject responseObject = null;
+        try {
+            responseObject = OpenSAMLUtil.fromDom(el);
+        } catch (WSSecurityException ex) {
+            LOG.debug(ex.getMessage(), ex);
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        if (!(responseObject instanceof org.opensaml.saml2.core.Response)) {
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        
+        // Validate the Response
+        validateSamlResponseProtocol((org.opensaml.saml2.core.Response)responseObject);
+        
+        // Validate the internal assertion(s)
+        TokenValidatorResponse validatorResponse = null;
+        List<Element> assertions = 
+            DOMUtils.getChildrenWithName(el, SAMLConstants.SAML20_NS, "Assertion");
+        
+        if (assertions.isEmpty()) {
+            LOG.debug("No Assertion extracted from SAML Response");
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+        Element token = assertions.get(0);
+            
+        List<TokenValidator> validators = protocol.getTokenValidators();
+        for (TokenValidator validator : validators) {
+            boolean canHandle = validator.canHandleToken(token);
+            if (canHandle) {
+                try {
+                    TokenValidatorRequest validatorRequest = 
+                        new TokenValidatorRequest(token, request.getCerts());
+                    validatorResponse = validator.validateAndProcessToken(validatorRequest, config);
+                } catch (ProcessingException ex) {
+                    throw ex;
+                } catch (Exception ex) {
+                    LOG.warn("Failed to validate token", ex);
+                    throw new ProcessingException(TYPE.TOKEN_INVALID);
+                }
+                break;
+            } else {
+                LOG.warn("No security token validator found for '" + token.getLocalName() + "'");
+                throw new ProcessingException(TYPE.BAD_REQUEST);
+            }
+        }
+        
+        /* TODO
+        SSOValidatorResponse validatorResponse = 
+            validateSamlSSOResponse(postBinding, samlResponse, requestState);
+            */
+        
+        FedizResponse fedResponse = new FedizResponse(
+                validatorResponse.getUsername(), validatorResponse.getIssuer(),
+                validatorResponse.getRoles(), validatorResponse.getClaims(),
+                validatorResponse.getAudience(),
+                null, // TODO
+                null, // TODO
+                token,
+                validatorResponse.getUniqueTokenId());
+
+        return fedResponse;
+    }
+    
+    /**
+     * Validate the received SAML Response as per the protocol
+     * @throws ProcessingException 
+     */
+    protected void validateSamlResponseProtocol(
+        org.opensaml.saml2.core.Response samlResponse
+    ) throws ProcessingException {
+        try {
+            SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+            protocolValidator.validateSamlResponse(samlResponse);
+        } catch (WSSecurityException ex) {
+            LOG.debug(ex.getMessage(), ex);
+            throw new ProcessingException(TYPE.INVALID_REQUEST);
+        }
+    }
+    
+    /**
+     * Validate the received SAML Response as per the Web SSO profile
+    protected SSOValidatorResponse validateSamlSSOResponse(
+        boolean postBinding,
+        org.opensaml.saml2.core.Response samlResponse,
+        RequestState requestState
+    ) {
+        try {
+            SAMLSSOResponseValidator ssoResponseValidator = new SAMLSSOResponseValidator();
+            ssoResponseValidator.setAssertionConsumerURL(
+                messageContext.getUriInfo().getAbsolutePath().toString());
+
+            ssoResponseValidator.setClientAddress(
+                 messageContext.getHttpServletRequest().getRemoteAddr());
+
+            ssoResponseValidator.setIssuerIDP(requestState.getIdpServiceAddress());
+            ssoResponseValidator.setRequestId(requestState.getSamlRequestId());
+            ssoResponseValidator.setSpIdentifier(requestState.getIssuerId());
+            ssoResponseValidator.setEnforceAssertionsSigned(enforceAssertionsSigned);
+            ssoResponseValidator.setEnforceKnownIssuer(enforceKnownIssuer);
+            ssoResponseValidator.setReplayCache(getReplayCache());
+
+            return ssoResponseValidator.validateSamlResponse(samlResponse, postBinding);
+        } catch (WSSecurityException ex) {
+            reportError("INVALID_SAML_RESPONSE");
+            throw ExceptionUtils.toBadRequestException(ex, null);
+        }
+    }
+    */
+
+    @Override
+    public RedirectionResponse createSignInRequest(HttpServletRequest request, FedizContext config)
+        throws ProcessingException {
+
+        String redirectURL = null;
+        try {
+            if (!(config.getProtocol() instanceof SAMLProtocol)) {
+                LOG.error("Unsupported protocol");
+                throw new IllegalStateException("Unsupported protocol");
+            }
+            
+            String issuerURL = resolveIssuer(request, config);
+            LOG.info("Issuer url: " + issuerURL);
+            if (issuerURL != null && issuerURL.length() > 0) {
+                redirectURL = issuerURL;
+            }
+            
+            AuthnRequestBuilder authnRequestBuilder = 
+                ((SAMLProtocol)config.getProtocol()).getAuthnRequestBuilder();
+            
+            Document doc = DOMUtils.createDocument();
+            doc.appendChild(doc.createElement("root"));
+     
+            // Create the AuthnRequest
+            String requestURL = request.getRequestURL().toString();
+            AuthnRequest authnRequest = 
+                authnRequestBuilder.createAuthnRequest(config.getName(), requestURL);
+            
+            if (((SAMLProtocol)config.getProtocol()).isSignRequest()) {
+                authnRequest.setDestination(redirectURL);
+            }
+            
+            Element authnRequestElement = OpenSAMLUtil.toDom(authnRequest, doc);
+            String authnRequestEncoded = encodeAuthnRequest(authnRequestElement);
+            
+            String webAppDomain = ((SAMLProtocol)config.getProtocol()).getWebAppDomain();
+            
+            RequestState requestState = new RequestState(requestURL,
+                                                         redirectURL,
+                                                         authnRequest.getID(),
+                                                         config.getName(),
+                                                         requestURL,
+                                                         webAppDomain,
+                                                         System.currentTimeMillis());
+            
+            String relayState = URLEncoder.encode(UUID.randomUUID().toString(), "UTF-8");
+            ((SAMLProtocol)config.getProtocol()).getStateManager().setRequestState(relayState, requestState);
+            
+            String urlEncodedRequest = 
+                URLEncoder.encode(authnRequestEncoded, "UTF-8");
+            
+            StringBuilder sb = new StringBuilder();
+            sb.append("SAMLRequest").append('=').append(urlEncodedRequest);
+            sb.append("&RelayState").append('=').append(relayState);
+            
+            if (((SAMLProtocol)config.getProtocol()).isSignRequest()) {
+                // TODO Sign the request
+            }
+            
+            String contextCookie = createCookie("RelayState",
+                                                relayState,
+                                                request.getRequestURI(),
+                                                webAppDomain,
+                                                ((SAMLProtocol)config.getProtocol()).getStateTimeToLive());
+            
+            RedirectionResponse response = new RedirectionResponse();
+            response.addHeader("Set-Cookie", contextCookie);
+            response.addHeader("Cache-Control", "no-cache, no-store");
+            response.addHeader("Pragma", "no-cache");
+            
+            redirectURL = redirectURL + "?" + sb.toString();
+            response.setRedirectionURL(redirectURL);
+            
+            return response;
+        } catch (Exception ex) {
+            LOG.error("Failed to create SignInRequest", ex);
+            throw new ProcessingException("Failed to create SignInRequest");
+        }
+    }
+    
+    protected String createCookie(String name, 
+                                  String value, 
+                                  String path,
+                                  String domain,
+                                  long stateTimeToLive) { 
+        
+        String contextCookie = name + "=" + value;
+        // Setting a specific path restricts the browsers
+        // to return a cookie only to the web applications
+        // listening on that specific context path
+        if (path != null) {
+            contextCookie += ";Path=" + path;
+        }
+        
+        // Setting a specific domain further restricts the browsers
+        // to return a cookie only to the web applications
+        // listening on the specific context path within a particular domain
+        if (domain != null) {
+            contextCookie += ";Domain=" + domain;
+        }
+        
+        // Keep the cookie across the browser restarts until it actually expires.
+        // Note that the Expires property has been deprecated but apparently is 
+        // supported better than 'max-age' property by different browsers 
+        // (Firefox, IE, etc)
+        Date expiresDate = new Date(System.currentTimeMillis() + stateTimeToLive);
+        String cookieExpires = getHttpDateFormat().format(expiresDate);
+        contextCookie += ";Expires=" + cookieExpires;
+        
+        return contextCookie;
+    }
+    
+    protected static SimpleDateFormat getHttpDateFormat() {
+        SimpleDateFormat dateFormat =
+            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+        TimeZone tZone = TimeZone.getTimeZone("GMT");
+        dateFormat.setTimeZone(tZone);
+        return dateFormat;
+    }
+    
+    protected String encodeAuthnRequest(Element authnRequest) throws IOException {
+        String requestMessage = DOM2Writer.nodeToString(authnRequest);
+
+        byte[] deflatedBytes = CompressionUtils.deflate(requestMessage.getBytes("UTF-8"));
+
+        return Base64.encode(deflatedBytes);
+    }
+
+    @Override
+    public RedirectionResponse createSignOutRequest(HttpServletRequest request, FedizContext config)
+        throws ProcessingException {
+
+        String redirectURL = null;
+        try {
+            if (!(config.getProtocol() instanceof FederationProtocol)) {
+                LOG.error("Unsupported protocol");
+                throw new IllegalStateException("Unsupported protocol");
+            }
+
+            String issuerURL = resolveIssuer(request, config);
+            LOG.info("Issuer url: " + issuerURL);
+            if (issuerURL != null && issuerURL.length() > 0) {
+                redirectURL = issuerURL;
+            }
+
+            StringBuilder sb = new StringBuilder();
+            sb.append(FederationConstants.PARAM_ACTION).append('=').append(FederationConstants.ACTION_SIGNOUT);
+
+            String logoutRedirectTo = config.getLogoutRedirectTo();
+            if (logoutRedirectTo != null && !logoutRedirectTo.isEmpty()) {
+
+                if (logoutRedirectTo.startsWith("/")) {
+                    logoutRedirectTo = extractFullContextPath(request).concat(logoutRedirectTo.substring(1));
+                } else {
+                    logoutRedirectTo = extractFullContextPath(request).concat(logoutRedirectTo);
+                }
+
+                LOG.debug("wreply=" + logoutRedirectTo);
+
+                sb.append('&').append(FederationConstants.PARAM_REPLY).append('=');
+                sb.append(URLEncoder.encode(logoutRedirectTo, "UTF-8"));
+            }
+
+            redirectURL = redirectURL + "?" + sb.toString();
+        } catch (Exception ex) {
+            LOG.error("Failed to create SignInRequest", ex);
+            throw new ProcessingException("Failed to create SignInRequest");
+        }
+        
+        RedirectionResponse response = new RedirectionResponse();
+        response.setRedirectionURL(redirectURL);
+        return response;
+    }
+/*
+    private String resolveSignInQuery(HttpServletRequest request, FedizContext config)
+        throws IOException, UnsupportedCallbackException, UnsupportedEncodingException {
+        Object signInQueryObj = ((FederationProtocol)config.getProtocol()).getSignInQuery();
+        String signInQuery = null;
+        if (signInQueryObj != null) {
+            if (signInQueryObj instanceof String) {
+                signInQuery = (String)signInQueryObj;
+            } else if (signInQueryObj instanceof CallbackHandler) {
+                CallbackHandler frCB = (CallbackHandler)signInQueryObj;
+                SignInQueryCallback callback = new SignInQueryCallback(request);
+                frCB.handle(new Callback[] {callback});
+                Map<String, String> signInQueryMap = callback.getSignInQueryParamMap();
+                StringBuilder sbQuery = new StringBuilder();
+                for (String key : signInQueryMap.keySet()) {
+                    if (sbQuery.length() > 0) {
+                        sbQuery.append("&");
+                    }
+                    sbQuery.append(key).append('=').
+                    append(URLEncoder.encode(signInQueryMap.get(key), "UTF-8"));
+                }
+                signInQuery = sbQuery.toString();
+               
+            }
+        }
+        return signInQuery;
+    }
+
+    private String resolveFreshness(HttpServletRequest request, FedizContext config) throws IOException,
+        UnsupportedCallbackException {
+        Object freshnessObj = ((FederationProtocol)config.getProtocol()).getFreshness();
+        String freshness = null;
+        if (freshnessObj != null) {
+            if (freshnessObj instanceof String) {
+                freshness = (String)freshnessObj;
+            } else if (freshnessObj instanceof CallbackHandler) {
+                CallbackHandler frCB = (CallbackHandler)freshnessObj;
+                FreshnessCallback callback = new FreshnessCallback(request);
+                frCB.handle(new Callback[] {callback});
+                freshness = callback.getFreshness();
+            }
+        }
+        return freshness;
+    }
+
+    private String resolveHomeRealm(HttpServletRequest request, FedizContext config) throws IOException,
+        UnsupportedCallbackException {
+        Object homeRealmObj = ((FederationProtocol)config.getProtocol()).getHomeRealm();
+        String homeRealm = null;
+        if (homeRealmObj != null) {
+            if (homeRealmObj instanceof String) {
+                homeRealm = (String)homeRealmObj;
+            } else if (homeRealmObj instanceof CallbackHandler) {
+                CallbackHandler hrCB = (CallbackHandler)homeRealmObj;
+                HomeRealmCallback callback = new HomeRealmCallback(request);
+                hrCB.handle(new Callback[] {callback});
+                homeRealm = callback.getHomeRealm();
+            }
+        }
+        return homeRealm;
+    }
+
+    private String resolveAuthenticationType(HttpServletRequest request, FedizContext config)
+        throws IOException, UnsupportedCallbackException {
+        Object wAuthObj = ((FederationProtocol)config.getProtocol()).getAuthenticationType();
+        String wAuth = null;
+        if (wAuthObj != null) {
+            if (wAuthObj instanceof String) {
+                wAuth = (String)wAuthObj;
+            } else if (wAuthObj instanceof CallbackHandler) {
+                CallbackHandler wauthCB = (CallbackHandler)wAuthObj;
+                WAuthCallback callback = new WAuthCallback(request);
+                wauthCB.handle(new Callback[] {callback});
+                wAuth = callback.getWauth();
+            }  
+        }
+        return wAuth;
+    }
+    
+    private String resolveRequest(HttpServletRequest request, FedizContext config)
+        throws IOException, UnsupportedCallbackException {
+        Object wReqObj = ((FederationProtocol)config.getProtocol()).getRequest();
+        String wReq = null;
+        if (wReqObj != null) {
+            if (wReqObj instanceof String) {
+                wReq = (String)wReqObj;
+            } else if (wReqObj instanceof CallbackHandler) {
+                CallbackHandler wauthCB = (CallbackHandler)wReqObj;
+                WReqCallback callback = new WReqCallback(request);
+                wauthCB.handle(new Callback[] {callback});
+                wReq = callback.getWreq();
+            }  
+        }
+        return wReq;
+    }
+*/
+    private String resolveIssuer(HttpServletRequest request, FedizContext config) throws IOException,
+        UnsupportedCallbackException {
+        Object issuerObj = config.getProtocol().getIssuer();
+        String issuerURL = null;
+        if (issuerObj instanceof String) {
+            issuerURL = (String)issuerObj;
+        } else if (issuerObj instanceof CallbackHandler) {
+            CallbackHandler issuerCB = (CallbackHandler)issuerObj;
+            IDPCallback callback = new IDPCallback(request);
+            issuerCB.handle(new Callback[] {callback});
+            issuerURL = callback.getIssuerUrl().toString();
+        }
+        return issuerURL;
+    }
+/*
+    private String resolveWTRealm(HttpServletRequest request, FedizContext config) throws IOException,
+        UnsupportedCallbackException {
+        Object wtRealmObj = ((FederationProtocol)config.getProtocol()).getRealm();
+        String wtRealm = null;
+        if (wtRealmObj != null) {
+            if (wtRealmObj instanceof String) {
+                wtRealm = (String)wtRealmObj;
+            } else if (wtRealmObj instanceof CallbackHandler) {
+                CallbackHandler hrCB = (CallbackHandler)wtRealmObj;
+                RealmCallback callback = new RealmCallback(request);
+                hrCB.handle(new Callback[] {callback});
+                wtRealm = callback.getRealm();
+            }
+        } else {
+            wtRealm = extractFullContextPath(request); //default value
+        }
+        return wtRealm;
+    }
+
+*/
+    private String extractFullContextPath(HttpServletRequest request) throws MalformedURLException {
+        String result = null;
+        String contextPath = request.getContextPath();
+        String requestUrl = request.getRequestURL().toString();
+        String requestPath = new URL(requestUrl).getPath();
+        // Cut request path of request url and add context path if not ROOT
+        if (requestPath != null && requestPath.length() > 0) {
+            int lastIndex = requestUrl.lastIndexOf(requestPath);
+            result = requestUrl.substring(0, lastIndex);
+        } else {
+            result = requestUrl;
+        }
+        if (contextPath != null && contextPath.length() > 0) {
+            // contextPath contains starting slash
+            result = result + contextPath + "/";
+        } else {
+            result = result + "/";
+        }
+        return result;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
new file mode 100644
index 0000000..0daeb2a
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
@@ -0,0 +1,176 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.IOException;
+import java.net.URL;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.config.CacheConfiguration;
+
+import org.apache.wss4j.common.cache.EHCacheManagerHolder;
+import org.apache.wss4j.common.util.Loader;
+
+/**
+ * An in-memory EHCache implementation of the SPStateManager interface. 
+ * The default TTL is 5 minutes.
+ */
+public class EHCacheSPStateManager implements SPStateManager {
+
+    public static final long DEFAULT_TTL = 60L * 5L;
+    public static final String REQUEST_CACHE_KEY = "cxf.fediz.samlp.request.state.cache";
+    public static final String RESPONSE_CACHE_KEY = "cxf.fediz.samlp.response.state.cache";
+    
+    private Ehcache requestCache;
+    private Ehcache responseCache;
+    private CacheManager cacheManager;
+    private long ttl = DEFAULT_TTL;
+    
+    public EHCacheSPStateManager(String configFile) {
+        this(getConfigFileURL(configFile));
+    }
+    
+    public EHCacheSPStateManager(URL configFileURL) {
+        this(EHCacheManagerHolder.getCacheManager("", configFileURL));
+    }
+    
+    public EHCacheSPStateManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+        
+        CacheConfiguration requestCC = EHCacheManagerHolder.getCacheConfiguration(REQUEST_CACHE_KEY, cacheManager);
+
+        Ehcache newCache = new Cache(requestCC);
+        requestCache = cacheManager.addCacheIfAbsent(newCache);
+        
+        CacheConfiguration responseCC = EHCacheManagerHolder.getCacheConfiguration(RESPONSE_CACHE_KEY, cacheManager);
+        
+        newCache = new Cache(responseCC);
+        responseCache = cacheManager.addCacheIfAbsent(newCache);
+    }
+    
+    private static URL getConfigFileURL(Object o) {
+        if (o instanceof String) {
+            try {
+                URL url = Loader.getResource((String)o);
+                if (url == null) {
+                    url = new URL((String)o);
+                }
+                return url;
+            } catch (IOException e) {
+                // Do nothing
+            }
+        } else if (o instanceof URL) {
+            return (URL)o;        
+        }
+        return null;
+    }
+    
+    /**
+     * Set a new (default) TTL value in seconds
+     * @param newTtl a new (default) TTL value in seconds
+     */
+    public void setTTL(long newTtl) {
+        ttl = newTtl;
+    }
+    
+    /**
+     * Get the (default) TTL value in seconds
+     * @return the (default) TTL value in seconds
+     */
+    public long getTTL() {
+        return ttl;
+    }
+    
+    public ResponseState getResponseState(String securityContextKey) {
+        Element element = responseCache.get(securityContextKey);
+        if (element != null) {
+            if (responseCache.isExpired(element)) {
+                responseCache.remove(securityContextKey);
+                return null;
+            }
+            return (ResponseState)element.getObjectValue();
+        }
+        return null;
+    }
+
+    public ResponseState removeResponseState(String securityContextKey) {
+        Element element = responseCache.get(securityContextKey);
+        if (element != null) {
+            responseCache.remove(securityContextKey);
+            return (ResponseState)element.getObjectValue();
+        }
+        return null;
+    }
+
+    public void setResponseState(String securityContextKey, ResponseState state) {
+        if (securityContextKey == null || "".equals(securityContextKey)) {
+            return;
+        }
+        
+        int parsedTTL = (int)ttl;
+        if (ttl != (long)parsedTTL) {
+            // Fall back to 5 minutes if the default TTL is set incorrectly
+            parsedTTL = 60 * 5;
+        }
+        Element element = new Element(securityContextKey, state);
+        element.setTimeToLive(parsedTTL);
+        element.setTimeToIdle(parsedTTL);
+        
+        responseCache.put(element);
+    }
+    
+    public void setRequestState(String relayState, RequestState state) {
+        if (relayState == null || "".equals(relayState)) {
+            return;
+        }
+        
+        int parsedTTL = (int)ttl;
+        if (ttl != (long)parsedTTL) {
+            // Fall back to 60 minutes if the default TTL is set incorrectly
+            parsedTTL = 3600;
+        }
+        
+        Element element = new Element(relayState, state);
+        element.setTimeToLive(parsedTTL);
+        element.setTimeToIdle(parsedTTL);
+        requestCache.put(element);
+    }
+
+    public RequestState removeRequestState(String relayState) {
+        Element element = requestCache.get(relayState);
+        if (element != null) {
+            requestCache.remove(relayState);
+            return (RequestState)element.getObjectValue();
+        }
+        return null;
+    }
+    
+    public void close() throws IOException {
+        if (cacheManager != null) {
+            cacheManager.shutdown();
+            cacheManager = null;
+            requestCache = null;
+            responseCache = null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
new file mode 100644
index 0000000..dfbf9ff
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.Serializable;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ResponseState implements Serializable {
+
+    private static final long serialVersionUID = -3247188797004342462L;
+    
+    private String assertion;
+    private String relayState;
+    private String webAppContext;
+    private String webAppDomain;
+    private long createdAt;
+    private long expiresAt;
+    
+    public ResponseState() {
+        
+    }
+    
+    public ResponseState(String assertion,
+                         String relayState,
+                         String webAppContext,
+                         String webAppDomain,
+                         long createdAt, 
+                         long expiresAt) {
+        this.assertion = assertion;
+        this.relayState = relayState;
+        this.webAppContext = webAppContext;
+        this.webAppDomain = webAppDomain;
+        this.createdAt = createdAt;
+        this.expiresAt = expiresAt;
+    }
+
+    public long getCreatedAt() {
+        return createdAt;
+    }
+    
+    public long getExpiresAt() {
+        return expiresAt;
+    }
+
+    public String getRelayState() {
+        return relayState;
+    }
+    
+    public String getWebAppContext() {
+        return webAppContext;
+    }
+
+    public String getWebAppDomain() {
+        return webAppDomain;
+    }
+    
+    public String getAssertion() {
+        return assertion;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
new file mode 100644
index 0000000..2269aa4
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
@@ -0,0 +1,255 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import org.w3c.dom.Document;
+
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SAMLKeyInfo;
+import org.apache.wss4j.common.saml.SAMLUtil;
+import org.apache.wss4j.dom.WSDocInfo;
+import org.apache.wss4j.dom.WSSConfig;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor;
+import org.opensaml.security.SAMLSignatureProfileValidator;
+import org.opensaml.xml.security.x509.BasicX509Credential;
+import org.opensaml.xml.signature.KeyInfo;
+import org.opensaml.xml.signature.Signature;
+import org.opensaml.xml.signature.SignatureValidator;
+import org.opensaml.xml.validation.ValidationException;
+import org.opensaml.xml.validation.ValidatorSuite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validate a SAML (1.1 or 2.0) Protocol Response. It validates the Response against the specs,
+ * the signature of the Response (if it exists), and any internal Assertion stored in the Response 
+ * - including any signature. It validates the status code of the Response as well.
+ */
+public class SAMLProtocolResponseValidator {
+    
+    public static final String SAML2_STATUSCODE_SUCCESS = 
+        "urn:oasis:names:tc:SAML:2.0:status:Success";
+    public static final String SAML1_STATUSCODE_SUCCESS = "Success";
+    
+    private static final Logger LOG = LoggerFactory.getLogger(SAMLProtocolResponseValidator.class);
+    
+    // private Validator signatureValidator = new SignatureTrustValidator();
+    
+    /**
+     * Validate a SAML 2 Protocol Response
+     * @param samlResponse
+     * @throws WSSecurityException
+     */
+    public void validateSamlResponse(
+        org.opensaml.saml2.core.Response samlResponse
+    ) throws WSSecurityException {
+        // Check the Status Code
+        if (samlResponse.getStatus() == null
+            || samlResponse.getStatus().getStatusCode() == null) {
+            LOG.debug("Either the SAML Response Status or StatusCode is null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        if (!SAML2_STATUSCODE_SUCCESS.equals(samlResponse.getStatus().getStatusCode().getValue())) {
+            LOG.debug(
+                "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue()
+                + "does not equal " + SAML2_STATUSCODE_SUCCESS
+            );
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        validateResponseAgainstSchemas(samlResponse);
+        validateResponseSignature(samlResponse);
+    }
+    
+    /**
+     * Validate a SAML 1.1 Protocol Response
+     * @param samlResponse
+     * @throws WSSecurityException
+     */
+    public void validateSamlResponse(
+        org.opensaml.saml1.core.Response samlResponse
+    ) throws WSSecurityException {
+        // Check the Status Code
+        if (samlResponse.getStatus() == null
+            || samlResponse.getStatus().getStatusCode() == null
+            || samlResponse.getStatus().getStatusCode().getValue() == null) {
+            LOG.debug("Either the SAML Response Status or StatusCode is null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        String statusValue = samlResponse.getStatus().getStatusCode().getValue().getLocalPart();
+        if (!SAML1_STATUSCODE_SUCCESS.equals(statusValue)) {
+            LOG.debug(
+                "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue()
+                + "does not equal " + SAML1_STATUSCODE_SUCCESS
+            );
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+
+        validateResponseAgainstSchemas(samlResponse);
+        validateResponseSignature(samlResponse);
+    }
+    
+    /**
+     * Validate the Response against the schemas
+     */
+    private void validateResponseAgainstSchemas(
+        org.opensaml.saml2.core.Response samlResponse
+    ) throws WSSecurityException {
+        // Validate SAML Response against schemas
+        ValidatorSuite schemaValidators = 
+            org.opensaml.Configuration.getValidatorSuite("saml2-core-schema-validator");
+        try {
+            schemaValidators.validate(samlResponse);
+        } catch (ValidationException e) {
+            LOG.debug("Saml Validation error: " + e.getMessage(), e);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    /**
+     * Validate the Response against the schemas
+     */
+    private void validateResponseAgainstSchemas(
+        org.opensaml.saml1.core.Response samlResponse
+    ) throws WSSecurityException {
+        // Validate SAML Response against schemas
+        ValidatorSuite schemaValidators = 
+            org.opensaml.Configuration.getValidatorSuite("saml1-core-schema-validator");
+        try {
+            schemaValidators.validate(samlResponse);
+        } catch (ValidationException e) {
+            LOG.debug("Saml Validation error: " + e.getMessage(), e);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    /**
+     * Validate the Response signature (if it exists)
+     */
+    private void validateResponseSignature(
+        org.opensaml.saml2.core.Response samlResponse
+    ) throws WSSecurityException {
+        if (!samlResponse.isSigned()) {
+            return;
+        }
+        
+        validateResponseSignature(
+            samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument()
+        );
+    }
+    
+    /**
+     * Validate the Response signature (if it exists)
+     */
+    private void validateResponseSignature(
+        org.opensaml.saml1.core.Response samlResponse
+    ) throws WSSecurityException {
+        if (!samlResponse.isSigned()) {
+            return;
+        }
+        
+        validateResponseSignature(
+            samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument()
+        );
+    }
+    
+    /**
+     * Validate the response signature
+     */
+    private void validateResponseSignature(
+        Signature signature, 
+        Document doc
+    ) throws WSSecurityException {
+        RequestData requestData = new RequestData();
+        WSSConfig wssConfig = WSSConfig.getNewInstance();
+        requestData.setWssConfig(wssConfig);
+        
+        SAMLKeyInfo samlKeyInfo = null;
+        
+        KeyInfo keyInfo = signature.getKeyInfo();
+        if (keyInfo != null) {
+            try {
+                samlKeyInfo = 
+                    SAMLUtil.getCredentialFromKeyInfo(
+                        keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(requestData, new WSDocInfo(doc)), 
+                        requestData.getSigVerCrypto()
+                    );
+            } catch (WSSecurityException ex) {
+                LOG.debug("Error in getting KeyInfo from SAML Response: " + ex.getMessage(), ex);
+                throw ex;
+            }
+        }
+        if (samlKeyInfo == null) {
+            LOG.debug("No KeyInfo supplied in the SAMLResponse signature");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Validate Signature against profiles
+        validateSignatureAgainstProfiles(signature, samlKeyInfo);
+
+        // Now verify trust on the signature
+        /* TODO Credential trustCredential = new Credential();
+        trustCredential.setPublicKey(samlKeyInfo.getPublicKey());
+        trustCredential.setCertificates(samlKeyInfo.getCerts());
+
+        try {
+            signatureValidator.validate(trustCredential, requestData);
+        } catch (WSSecurityException e) {
+            LOG.debug("Error in validating signature on SAML Response: " + e.getMessage(), e);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        */
+    }
+    
+    /**
+     * Validate a signature against the profiles
+     */
+    private void validateSignatureAgainstProfiles(
+        Signature signature, 
+        SAMLKeyInfo samlKeyInfo
+    ) throws WSSecurityException {
+        // Validate Signature against profiles
+        SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator();
+        try {
+            validator.validate(signature);
+        } catch (ValidationException ex) {
+            LOG.debug("Error in validating the SAML Signature: " + ex.getMessage(), ex);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+
+        BasicX509Credential credential = new BasicX509Credential();
+        if (samlKeyInfo.getCerts() != null) {
+            credential.setEntityCertificate(samlKeyInfo.getCerts()[0]);
+        } else if (samlKeyInfo.getPublicKey() != null) {
+            credential.setPublicKey(samlKeyInfo.getPublicKey());
+        } else {
+            LOG.debug("Can't get X509Certificate or PublicKey to verify signature");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        SignatureValidator sigValidator = new SignatureValidator(credential);
+        try {
+            sigValidator.validate(signature);
+        } catch (ValidationException ex) {
+            LOG.debug("Error in validating the SAML Signature: " + ex.getMessage(), ex);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
new file mode 100644
index 0000000..4d75027
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
@@ -0,0 +1,330 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.util.Date;
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+import org.apache.wss4j.common.cache.ReplayCache;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.AuthnStatement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validate a SAML 2.0 Protocol Response according to the Web SSO profile. The Response
+ * should be validated by the SAMLProtocolResponseValidator first.
+ */
+public class SAMLSSOResponseValidator {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(SAMLSSOResponseValidator.class);
+    
+    private String issuerIDP;
+    private String assertionConsumerURL;
+    private String clientAddress;
+    private String requestId;
+    private String spIdentifier;
+    private boolean enforceAssertionsSigned = true;
+    private boolean enforceKnownIssuer = true;
+    private ReplayCache replayCache;
+    
+    /**
+     * Enforce that Assertions must be signed if the POST binding was used. The default is true.
+     */
+    public void setEnforceAssertionsSigned(boolean enforceAssertionsSigned) {
+        this.enforceAssertionsSigned = enforceAssertionsSigned;
+    }
+    
+    /**
+     * Enforce that the Issuer of the received Response/Assertion is known. The default is true.
+     */
+    public void setEnforceKnownIssuer(boolean enforceKnownIssuer) {
+        this.enforceKnownIssuer = enforceKnownIssuer;
+    }
+    
+    /**
+     * Validate a SAML 2 Protocol Response
+     * @param samlResponse
+     * @param postBinding
+     * @return a SSOValidatorResponse object
+     * @throws WSSecurityException
+     */
+    public SSOValidatorResponse validateSamlResponse(
+        org.opensaml.saml2.core.Response samlResponse,
+        boolean postBinding
+    ) throws WSSecurityException {
+        // Check the Issuer
+        validateIssuer(samlResponse.getIssuer());
+
+        // The Response must contain at least one Assertion.
+        if (samlResponse.getAssertions() == null || samlResponse.getAssertions().isEmpty()) {
+            LOG.debug("The Response must contain at least one Assertion");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // The Response must contain a Destination that matches the assertionConsumerURL if it is
+        // signed
+        String destination = samlResponse.getDestination();
+        if (samlResponse.isSigned()
+            && (destination == null || !destination.equals(assertionConsumerURL))) {
+            LOG.debug("The Response must contain a destination that matches the assertion consumer URL");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Validate Assertions
+        boolean foundValidSubject = false;
+        Date sessionNotOnOrAfter = null;
+        for (org.opensaml.saml2.core.Assertion assertion : samlResponse.getAssertions()) {
+            // Check the Issuer
+            if (assertion.getIssuer() == null) {
+                LOG.debug("Assertion Issuer must not be null");
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+            validateIssuer(assertion.getIssuer());
+            
+            if (enforceAssertionsSigned && postBinding && assertion.getSignature() == null) {
+                LOG.debug("If the HTTP Post binding is used to deliver the Response, "
+                         + "the enclosed assertions must be signed");
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+            
+            // Check for AuthnStatements and validate the Subject accordingly
+            if (assertion.getAuthnStatements() != null
+                && !assertion.getAuthnStatements().isEmpty()) {
+                org.opensaml.saml2.core.Subject subject = assertion.getSubject();
+                if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) {
+                    validateAudienceRestrictionCondition(assertion.getConditions());
+                    foundValidSubject = true;
+                    // Store Session NotOnOrAfter
+                    for (AuthnStatement authnStatment : assertion.getAuthnStatements()) {
+                        if (authnStatment.getSessionNotOnOrAfter() != null) {
+                            sessionNotOnOrAfter = authnStatment.getSessionNotOnOrAfter().toDate();
+                        }
+                    }
+                }
+            }
+            
+        }
+        
+        if (!foundValidSubject) {
+            LOG.debug("The Response did not contain any Authentication Statement that matched "
+                     + "the Subject Confirmation criteria");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        SSOValidatorResponse validatorResponse = new SSOValidatorResponse();
+        validatorResponse.setResponseId(samlResponse.getID());
+        validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter);
+        // the assumption for now is that SAMLResponse will contain only a single assertion
+        Element assertionElement = samlResponse.getAssertions().get(0).getDOM();
+        validatorResponse.setAssertion(DOM2Writer.nodeToString(assertionElement.cloneNode(true)));
+        return validatorResponse;
+    }
+    
+    /**
+     * Validate the Issuer (if it exists)
+     */
+    private void validateIssuer(org.opensaml.saml2.core.Issuer issuer) throws WSSecurityException {
+        if (issuer == null) {
+            return;
+        }
+        
+        // Issuer value must match (be contained in) Issuer IDP
+        if (enforceKnownIssuer && !issuerIDP.startsWith(issuer.getValue())) {
+            LOG.debug("Issuer value: " + issuer.getValue() + " does not match issuer IDP: " 
+                + issuerIDP);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Format must be nameid-format-entity
+        if (issuer.getFormat() != null
+            && !SAML2Constants.NAMEID_FORMAT_ENTITY.equals(issuer.getFormat())) {
+            LOG.debug("Issuer format is not null and does not equal: " 
+                + SAML2Constants.NAMEID_FORMAT_ENTITY);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    /**
+     * Validate the Subject (of an Authentication Statement).
+     */
+    private boolean validateAuthenticationSubject(
+        org.opensaml.saml2.core.Subject subject, String id, boolean postBinding
+    ) throws WSSecurityException {
+        if (subject.getSubjectConfirmations() == null) {
+            return false;
+        }
+        // We need to find a Bearer Subject Confirmation method
+        for (org.opensaml.saml2.core.SubjectConfirmation subjectConf 
+            : subject.getSubjectConfirmations()) {
+            if (SAML2Constants.CONF_BEARER.equals(subjectConf.getMethod())) {
+                validateSubjectConfirmation(subjectConf.getSubjectConfirmationData(), id, postBinding);
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Validate a (Bearer) Subject Confirmation
+     */
+    private void validateSubjectConfirmation(
+        org.opensaml.saml2.core.SubjectConfirmationData subjectConfData, String id, boolean postBinding
+    ) throws WSSecurityException {
+        if (subjectConfData == null) {
+            LOG.debug("Subject Confirmation Data of a Bearer Subject Confirmation is null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Recipient must match assertion consumer URL
+        String recipient = subjectConfData.getRecipient();
+        if (recipient == null || !recipient.equals(assertionConsumerURL)) {
+            LOG.debug("Recipient " + recipient + " does not match assertion consumer URL "
+                + assertionConsumerURL);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // We must have a NotOnOrAfter timestamp
+        if (subjectConfData.getNotOnOrAfter() == null
+            || subjectConfData.getNotOnOrAfter().isBeforeNow()) {
+            LOG.debug("Subject Conf Data does not contain NotOnOrAfter or it has expired");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Need to keep bearer assertion IDs based on NotOnOrAfter to detect replay attacks
+        if (postBinding && replayCache != null) {
+            if (replayCache.contains(id)) {
+                Date expires = subjectConfData.getNotOnOrAfter().toDate();
+                Date currentTime = new Date();
+                long ttl = expires.getTime() - currentTime.getTime();
+                replayCache.add(id, ttl / 1000L);
+            } else {
+                LOG.debug("Replay attack with token id: " + id);
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+        }
+        
+        // Check address
+        if (subjectConfData.getAddress() != null
+            && !subjectConfData.getAddress().equals(clientAddress)) {
+            LOG.debug("Subject Conf Data address " + subjectConfData.getAddress() + " does match"
+                     + " client address " + clientAddress);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // It must not contain a NotBefore timestamp
+        if (subjectConfData.getNotBefore() != null) {
+            LOG.debug("The Subject Conf Data must not contain a NotBefore timestamp");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // InResponseTo must match the AuthnRequest request Id
+        if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) {
+            LOG.debug("The InResponseTo String does match the original request id " + requestId);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+    }
+    
+    private void validateAudienceRestrictionCondition(
+        org.opensaml.saml2.core.Conditions conditions
+    ) throws WSSecurityException {
+        if (conditions == null) {
+            LOG.debug("Conditions are null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        List<AudienceRestriction> audienceRestrs = conditions.getAudienceRestrictions();
+        if (!matchSaml2AudienceRestriction(spIdentifier, audienceRestrs)) {
+            LOG.debug("Assertion does not contain unique subject provider identifier " 
+                     + spIdentifier + " in the audience restriction conditions");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    
+    private boolean matchSaml2AudienceRestriction(
+        String appliesTo, List<AudienceRestriction> audienceRestrictions
+    ) {
+        boolean found = false;
+        if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
+            for (AudienceRestriction audienceRestriction : audienceRestrictions) {
+                if (audienceRestriction.getAudiences() != null) {
+                    for (org.opensaml.saml2.core.Audience audience : audienceRestriction.getAudiences()) {
+                        if (appliesTo.equals(audience.getAudienceURI())) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return found;
+    }
+
+    public String getIssuerIDP() {
+        return issuerIDP;
+    }
+
+    public void setIssuerIDP(String issuerIDP) {
+        this.issuerIDP = issuerIDP;
+    }
+
+    public String getAssertionConsumerURL() {
+        return assertionConsumerURL;
+    }
+
+    public void setAssertionConsumerURL(String assertionConsumerURL) {
+        this.assertionConsumerURL = assertionConsumerURL;
+    }
+
+    public String getClientAddress() {
+        return clientAddress;
+    }
+
+    public void setClientAddress(String clientAddress) {
+        this.clientAddress = clientAddress;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+
+    public String getSpIdentifier() {
+        return spIdentifier;
+    }
+
+    public void setSpIdentifier(String spIdentifier) {
+        this.spIdentifier = spIdentifier;
+    }
+    
+    public void setReplayCache(ReplayCache replayCache) {
+        this.replayCache = replayCache;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
new file mode 100644
index 0000000..d55dce0
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * SSO Service Provider State Manager.
+ * 
+ * TODO: review the possibility of working with the Servlet HTTPSession
+ * instead; in that case it can be tricky to configure various containers 
+ * (Tomcat, Jetty) to make sure the cookies are shared across multiple 
+ * war contexts which will be needed if RequestAssertionConsumerService
+ * needs to be run in its own war file instead of having every application 
+ * war on the SP side have a dedicated RequestAssertionConsumerService endpoint   
+ */
+public interface SPStateManager extends Closeable {
+    
+    void setRequestState(String relayState, RequestState state);
+    RequestState removeRequestState(String relayState);
+    
+    void setResponseState(String contextKey, ResponseState state);
+    ResponseState getResponseState(String contextKey);
+    ResponseState removeResponseState(String contextKey);
+    
+    void close() throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
new file mode 100644
index 0000000..13bd839
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.util.Date;
+
+/**
+ * Some information that encapsulates a successful validation by the SAMLSSOResponseValidator
+ */
+public class SSOValidatorResponse {
+    private Date sessionNotOnOrAfter;
+    private String responseId;
+    private String assertion;
+
+    public String getAssertion() {
+        return assertion;
+    }
+
+    public void setAssertion(String assertion) {
+        this.assertion = assertion;
+    }
+    
+    public Date getSessionNotOnOrAfter() {
+        return sessionNotOnOrAfter;
+    }
+    
+    public void setSessionNotOnOrAfter(Date sessionNotOnOrAfter) {
+        this.sessionNotOnOrAfter = sessionNotOnOrAfter;
+    }
+    
+    public String getResponseId() {
+        return responseId;
+    }
+    
+    public void setResponseId(String responseId) {
+        this.responseId = responseId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/resources/schemas/FedizConfig.xsd
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/resources/schemas/FedizConfig.xsd b/plugins/core/src/main/resources/schemas/FedizConfig.xsd
index 3420b09..748b8a7 100644
--- a/plugins/core/src/main/resources/schemas/FedizConfig.xsd
+++ b/plugins/core/src/main/resources/schemas/FedizConfig.xsd
@@ -117,6 +117,8 @@
 					<xs:element ref="stateTimeToLive" />
 					<xs:element ref="webAppDomain" />
 					<xs:element ref="authnRequestBuilder"/>
+					<xs:element ref="stateManager"/>
+					<xs:element ref="tokenValidators" />
 				</xs:sequence>
 				<xs:attribute name="version" use="required" type="xs:string" />
 			</xs:extension>
@@ -132,6 +134,7 @@
 	<xs:element name="stateTimeToLive" type="xs:long" default="120000" />
 	<xs:element name="webAppDomain" type="xs:string" />
 	<xs:element name="authnRequestBuilder" type="xs:string" />
+	<xs:element name="stateManager" type="xs:string" />
 	
 	<xs:complexType name="protocolType" abstract="true">
 	    <xs:sequence>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java b/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
index e2d774d..ce23c0c 100644
--- a/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
+++ b/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
@@ -195,6 +195,7 @@ public class FederationAuthenticator extends LoginAuthenticator {
                     FedizRequest wfReq = new FedizRequest();
                     wfReq.setAction(action);
                     wfReq.setResponseToken(responseToken);
+                    wfReq.setState(request.getParameter("RelayState"));
 
                     X509Certificate certs[] = 
                         (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java b/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
index 93491b6..3e20030 100644
--- a/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
+++ b/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
@@ -51,6 +51,7 @@ public class FederationAuthenticationFilter extends AbstractAuthenticationProces
         FedizRequest wfReq = new FedizRequest();
         wfReq.setAction(wa);
         wfReq.setResponseToken(responseToken);
+        wfReq.setState(request.getParameter("RelayState"));
         
         X509Certificate certs[] = 
             (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java b/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
index 255a941..466f7c3 100644
--- a/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
+++ b/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
@@ -64,6 +64,7 @@ public class FederationAuthenticationFilter extends AbstractProcessingFilter {
         FedizRequest wfReq = new FedizRequest();
         wfReq.setAction(wa);
         wfReq.setResponseToken(responseToken);
+        wfReq.setState(request.getParameter("RelayState"));
         
         X509Certificate certs[] = 
             (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java b/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
index 7daddd0..ce49565 100644
--- a/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
+++ b/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
@@ -421,6 +421,7 @@ public class FederationAuthenticator extends FormAuthenticator {
                 FedizRequest wfReq = new FedizRequest();
                 wfReq.setAction(action);
                 wfReq.setResponseToken(responseToken);
+                wfReq.setState(request.getParameter("RelayState"));
                 
                 X509Certificate certs[] = 
                     (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
@@ -436,7 +437,6 @@ public class FederationAuthenticator extends FormAuthenticator {
                     return false;
                 }
                 
-                
                 // Validate the AudienceRestriction in Security Token (e.g. SAML) 
                 // against the configured list of audienceURIs
                 if (wfRes.getAudience() != null) {

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
----------------------------------------------------------------------
diff --git a/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java b/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
index af57bd1..e62275e 100644
--- a/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
+++ b/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
@@ -172,5 +172,72 @@ public final class HTTPTestUtils {
             }
         }
     }
+    
+    public static String sendHttpGetForSAMLSSO(String url, String user, 
+                                     String password, int idpPort) throws Exception {
+        return sendHttpGetForSAMLSSO(url, user, password, 200, 200, idpPort);
+    }
+    
+    public static String sendHttpGetForSAMLSSO(String url, String user, String password, 
+                                     int returnCodeIDP, int returnCodeRP, int idpPort)
+        throws Exception {
+        
+        CloseableHttpClient httpClient = null;
+        try {
+            CredentialsProvider credsProvider = new BasicCredentialsProvider();
+            credsProvider.setCredentials(
+                new AuthScope("localhost", idpPort), 
+                new UsernamePasswordCredentials(user, password));
+
+            KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());
+            FileInputStream instream = new FileInputStream(new File("./target/test-classes/client.jks"));
+            try {
+                trustStore.load(instream, "clientpass".toCharArray());
+            } finally {
+                try {
+                    instream.close();
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+            }
+
+            SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
+            sslContextBuilder.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy());
+            sslContextBuilder.loadKeyMaterial(trustStore, "clientpass".toCharArray());
+            
+            SSLContext sslContext = sslContextBuilder.build();
+            SSLConnectionSocketFactory sslSocketFactory = 
+                new SSLConnectionSocketFactory(sslContext);
+            
+            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+            httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
+            httpClientBuilder.setSSLSocketFactory(sslSocketFactory);
+            httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());
+            
+            httpClient = httpClientBuilder.build();
+            
+            HttpGet httpget = new HttpGet(url);
+
+            HttpResponse response = httpClient.execute(httpget);
+            HttpEntity entity = response.getEntity();
+
+            System.out.println(response.getStatusLine());
+            if (entity != null) {
+                System.out.println("Response content length: " + entity.getContentLength());
+            }
+            Assert.assertTrue("RP HTTP Response code: " + response.getStatusLine().getStatusCode()
+                              + " [Expected: " + returnCodeRP + "]",
+                              returnCodeRP == response.getStatusLine().getStatusCode());
+
+            return EntityUtils.toString(entity);
+        } finally {
+            // When HttpClient instance is no longer needed,
+            // shut down the connection manager to ensure
+            // immediate deallocation of all system resources
+            if (httpClient != null) {
+                httpClient.close();
+            }
+        }
+    }
 
 }