keycloak-aplcache

saml sp

9/17/2015 3:00:57 PM

Changes

pom.xml 5(+5 -0)

Details

diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 309337f..8e4d163 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -10,7 +10,6 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
 import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
 import org.keycloak.dom.saml.v2.assertion.AttributeType;
 import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
-import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
 import org.keycloak.dom.saml.v2.assertion.NameIDType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType;
 import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
@@ -30,26 +29,17 @@ import org.keycloak.saml.SAMLRequestParser;
 import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.protocol.saml.SamlProtocolUtils;
 import org.keycloak.saml.common.constants.GeneralConstants;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ProcessingException;
-import org.keycloak.saml.common.util.DocumentUtil;
-import org.keycloak.saml.common.util.StaxParserUtil;
-import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
-import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
 import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
 import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
-import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
-import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
+import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
 import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
 import org.keycloak.saml.processing.web.util.PostBindingUtil;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.messages.Messages;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
@@ -62,9 +52,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
-import javax.xml.namespace.QName;
 import java.io.IOException;
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.util.List;
@@ -156,7 +144,7 @@ public class SAMLEndpoint {
         }
 
         protected abstract String getBindingType();
-        protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
+        protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
         protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
         protected abstract SAMLDocumentHolder extractResponseDocument(String response);
         protected PublicKey getIDPKey() {
@@ -189,7 +177,7 @@ public class SAMLEndpoint {
             }
             if (config.isValidateSignature()) {
                 try {
-                    verifySignature(holder);
+                    verifySignature(GeneralConstants.SAML_REQUEST_KEY, holder);
                 } catch (VerificationException e) {
                     logger.error("validation failed", e);
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
@@ -276,7 +264,7 @@ public class SAMLEndpoint {
         protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
 
             try {
-                AssertionType assertion = getAssertion(responseType);
+                AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
                 SubjectType subject = assertion.getSubject();
                 SubjectType.STSubType subType = subject.getSubType();
                 NameIDType subjectNameID = (NameIDType) subType.getBaseID();
@@ -336,22 +324,6 @@ public class SAMLEndpoint {
 
 
 
-        private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
-            List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
-
-            if (assertions.isEmpty()) {
-                throw new IdentityBrokerException("No assertion from response.");
-            }
-
-            ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
-            EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
-
-            if (encryptedAssertion != null) {
-                decryptAssertion(responseType, realm.getPrivateKey());
-
-            }
-            return responseType.getAssertions().get(0).getAssertion();
-        }
 
         public Response handleSamlResponse(String samlResponse, String relayState) {
             SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
@@ -365,7 +337,7 @@ public class SAMLEndpoint {
             }
             if (config.isValidateSignature()) {
                 try {
-                    verifySignature(holder);
+                    verifySignature(GeneralConstants.SAML_RESPONSE_KEY, holder);
                 } catch (VerificationException e) {
                     logger.error("validation failed", e);
                     event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
@@ -408,43 +380,14 @@ public class SAMLEndpoint {
         }
 
 
-        protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
-            SAML2Response saml2Response = new SAML2Response();
-
-            try {
-                Document doc = saml2Response.convert(responseType);
-                Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
-
-                if (enc == null) {
-                    throw new IdentityBrokerException("No encrypted assertion found.");
-                }
-
-                String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
-                Document newDoc = DocumentUtil.createDocument();
-                Node importedNode = newDoc.importNode(enc, true);
-                newDoc.appendChild(importedNode);
-
-                Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
-                SAMLParser parser = new SAMLParser();
-
-                JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
-                AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
-                        .getNodeAsStream(decryptedDocumentElement)));
 
-                responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
-
-                return responseType;
-            } catch (Exception e) {
-                throw new IdentityBrokerException("Could not decrypt assertion.", e);
-            }
-        }
 
 
     }
 
     protected class PostBinding extends Binding {
         @Override
-        protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+        protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
             SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
         }
 
@@ -467,9 +410,9 @@ public class SAMLEndpoint {
 
     protected class RedirectBinding extends Binding {
         @Override
-        protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+        protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
             PublicKey publicKey = getIDPKey();
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
         }
 
 
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
index d07bffa..066a32a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
@@ -5,7 +5,7 @@ package org.keycloak.adapters;
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public interface AdapterTokenStore {
+public interface AdapterTokenStore extends AdapterSessionStore {
 
     /**
      * Impl can validate if current token exists and perform refreshing if it exists and is expired
@@ -39,6 +39,4 @@ public interface AdapterTokenStore {
      */
     void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
 
-    void saveRequest();
-    boolean restoreRequest();
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index fa33363..1dfe41d 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -26,7 +26,7 @@ public class OAuthRequestAuthenticator {
     protected KeycloakDeployment deployment;
     protected RequestAuthenticator reqAuthenticator;
     protected int sslRedirectPort;
-    protected AdapterTokenStore tokenStore;
+    protected AdapterSessionStore tokenStore;
     protected String tokenString;
     protected String idTokenString;
     protected IDToken idToken;
@@ -36,7 +36,7 @@ public class OAuthRequestAuthenticator {
     protected String refreshToken;
     protected String strippedOauthParametersRequestUri;
 
-    public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterTokenStore tokenStore) {
+    public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterSessionStore tokenStore) {
         this.reqAuthenticator = requestAuthenticator;
         this.facade = facade;
         this.deployment = deployment;
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java
new file mode 100755
index 0000000..8c5f2e2
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java
@@ -0,0 +1,10 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AdapterSessionStore {
+    void saveRequest();
+    boolean restoreRequest();
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
index 47eeee1..a4c55cb 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
@@ -7,5 +7,6 @@ package org.keycloak.adapters;
 public enum AuthOutcome {
     NOT_ATTEMPTED,
     FAILED,
-    AUTHENTICATED
+    AUTHENTICATED,
+    LOGGED_OUT
 }
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
index 90c2dc7..49a39b4 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
@@ -33,6 +33,13 @@ public interface HttpFacade {
          */
         boolean isSecure();
 
+        /**
+         * Get first query or form param
+         *
+         * @param param
+         * @return
+         */
+        String getFirstParam(String param);
         String getQueryParamValue(String param);
         Cookie getCookie(String cookieName);
         String getHeader(String name);
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
new file mode 100755
index 0000000..8225ae2
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
@@ -0,0 +1,67 @@
+package org.keycloak.adapters;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Maps external principal and SSO id to internal local http session id
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InMemorySessionIdMapper implements SessionIdMaper {
+    ConcurrentHashMap<String, String> ssoToSession = new ConcurrentHashMap<>();
+    ConcurrentHashMap<String, String> sessionToSso = new ConcurrentHashMap<>();
+    ConcurrentHashMap<String, Set<String>> principalToSession = new ConcurrentHashMap<>();
+    ConcurrentHashMap<String, String> sessionToPrincipal = new ConcurrentHashMap<>();
+
+    @Override
+    public Set<String> getUserSessions(String principal) {
+        Set<String> lookup = principalToSession.get(principal);
+        if (lookup == null) return null;
+        Set<String> copy = new HashSet<>();
+        copy.addAll(lookup);
+        return copy;
+    }
+
+    @Override
+    public String getSessionFromSSO(String sso) {
+        return ssoToSession.get(sso);
+    }
+
+    @Override
+    public void map(String sso, String principal, String session) {
+        ssoToSession.put(sso, session);
+        sessionToSso.put(session, sso);
+        Set<String> userSessions = principalToSession.get(principal);
+        if (userSessions == null) {
+            final Set<String> tmp = Collections.synchronizedSet(new HashSet<String>());
+            userSessions = principalToSession.putIfAbsent(principal, tmp);
+            if (userSessions == null) {
+                userSessions = tmp;
+            }
+        }
+        userSessions.add(session);
+        sessionToPrincipal.put(session, principal);
+    }
+
+    @Override
+    public void removeSession(String session) {
+        String sso = sessionToSso.remove(session);
+        if (sso != null) {
+            ssoToSession.remove(sso);
+        }
+        String principal =  sessionToPrincipal.remove(session);
+        if (principal != null) {
+            Set<String> sessions = principalToSession.get(principal);
+            sessions.remove(session);
+            if (sessions.isEmpty()) {
+                principalToSession.remove(principal, sessions);
+            }
+        }
+    }
+
+
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java
new file mode 100755
index 0000000..8e86b1e
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java
@@ -0,0 +1,17 @@
+package org.keycloak.adapters;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SessionIdMaper {
+    Set<String> getUserSessions(String principal);
+
+    String getSessionFromSSO(String sso);
+
+    void map(String sso, String principal, String session);
+
+    void removeSession(String session);
+}
diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
index 2ff677f..490d42d 100755
--- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
+++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
@@ -34,6 +34,11 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
     protected class RequestFacade implements OIDCHttpFacade.Request {
 
         @Override
+        public String getFirstParam(String param) {
+            throw new RuntimeException("NOT IMPLEMENTED");
+        }
+
+        @Override
         public String getMethod() {
             return requestContext.getMethod();
         }
diff --git a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 243de1c..2bc1d8c 100755
--- a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
 
 import javax.servlet.ServletRequest;
 
@@ -23,7 +24,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
 
     @Override
     public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
-        return new JettySessionTokenStore(request, resolvedDeployment);
+        return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
     }
 
     @Override
diff --git a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 0d1bf3b..a319266 100755
--- a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
 
 import javax.servlet.ServletRequest;
 
@@ -23,7 +24,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
 
     @Override
     public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
-        return new JettySessionTokenStore(request, resolvedDeployment);
+        return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
     }
 
     @Override
diff --git a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 90b3c6a..2aec5ef 100755
--- a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
 
 import javax.servlet.ServletRequest;
 
@@ -38,6 +39,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
 
     @Override
     public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
-        return new JettySessionTokenStore(request, resolvedDeployment);
+        return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
     }
 }
diff --git a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
index 8ffae79..145f5ef 100755
--- a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
+++ b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
@@ -18,6 +18,7 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public class JettyHttpFacade implements HttpFacade {
+    public final static String __J_METHOD = "org.eclipse.jetty.security.HTTP_METHOD";
     protected org.eclipse.jetty.server.Request request;
     protected HttpServletResponse response;
     protected RequestFacade requestFacade = new RequestFacade();
@@ -59,6 +60,11 @@ public class JettyHttpFacade implements HttpFacade {
         }
 
         @Override
+        public String getFirstParam(String param) {
+            return request.getParameter(param);
+        }
+
+        @Override
         public boolean isSecure() {
             return request.isSecure();
         }
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index e2afb94..ed8744e 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -183,6 +183,11 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
             return new Request() {
 
                 @Override
+                public String getFirstParam(String param) {
+                    return servletRequest.getParameter(param);
+                }
+
+                @Override
                 public String getMethod() {
                     return servletRequest.getMethod();
                 }
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
old mode 100644
new mode 100755
index 478f182..e2e5ba4
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
@@ -33,6 +33,11 @@ class WrappedHttpServletRequest implements Request {
     }
 
     @Override
+    public String getFirstParam(String param) {
+        return request.getParameter(param);
+    }
+
+    @Override
     public String getMethod() {
         return request.getMethod();
     }
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
old mode 100644
new mode 100755
index 602f8ca..a5f5b07
--- a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
@@ -2,10 +2,9 @@ package org.keycloak.adapters.springsecurity.token;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterSessionStore;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import javax.servlet.http.HttpServletRequest;
@@ -32,7 +31,7 @@ public class SpringSecurityAdapterTokenStoreFactoryTest {
 
     @Test
     public void testCreateAdapterTokenStore() throws Exception {
-        AdapterTokenStore store = factory.createAdapterTokenStore(deployment, request);
+        AdapterSessionStore store = factory.createAdapterTokenStore(deployment, request);
         assertNotNull(store);
         assertTrue(store instanceof SpringSecurityTokenStore);
     }
diff --git a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
index a72ad5d..b195f39 100755
--- a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
+++ b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
@@ -65,6 +65,11 @@ public class CatalinaHttpFacade implements HttpFacade {
         }
 
         @Override
+        public String getFirstParam(String param) {
+            return request.getParameter(param);
+        }
+
+        @Override
         public String getQueryParamValue(String paramName) {
             if (queryParameters == null) {
                 queryParameters = UriUtils.decodeQueryString(request.getQueryString());
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
new file mode 100755
index 0000000..f1ffbf0
--- /dev/null
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
@@ -0,0 +1,32 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.keycloak.adapters.AdapterSessionStore;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaAdapterSessionStore implements AdapterSessionStore {
+    protected Request request;
+    protected AbstractKeycloakAuthenticatorValve valve;
+
+    public CatalinaAdapterSessionStore(Request request, AbstractKeycloakAuthenticatorValve valve) {
+        this.request = request;
+        this.valve = valve;
+    }
+
+    public void saveRequest() {
+        try {
+            valve.keycloakSaveRequest(request);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean restoreRequest() {
+        return valve.keycloakRestoreRequest(request);
+    }
+}
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
index a91ede0..e0d3ca6 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
@@ -19,26 +19,23 @@ import java.util.logging.Logger;
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class CatalinaSessionTokenStore implements AdapterTokenStore {
+public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore implements AdapterTokenStore {
 
     private static final Logger log = Logger.getLogger("" + CatalinaSessionTokenStore.class);
 
-    private Request request;
     private KeycloakDeployment deployment;
     private CatalinaUserSessionManagement sessionManagement;
     protected GenericPrincipalFactory principalFactory;
-    protected AbstractKeycloakAuthenticatorValve valve;
 
 
     public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment,
                                      CatalinaUserSessionManagement sessionManagement,
                                      GenericPrincipalFactory principalFactory,
                                      AbstractKeycloakAuthenticatorValve valve) {
-        this.request = request;
+        super(request, valve);
         this.deployment = deployment;
         this.sessionManagement = sessionManagement;
         this.principalFactory = principalFactory;
-        this.valve = valve;
     }
 
     @Override
@@ -169,17 +166,4 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
         // no-op
     }
 
-    @Override
-    public void saveRequest() {
-        try {
-            valve.keycloakSaveRequest(request);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public boolean restoreRequest() {
-        return valve.keycloakRestoreRequest(request);
-    }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
index 71382ff..790078f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -60,7 +60,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
                 Integer code = servePage(exchange, errorPage);
                 return new ChallengeResult(true, code);
             }
-            UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+            UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
             if (challenge.challenge(facade)) {
                 return new ChallengeResult(true, exchange.getResponseCode());
             }
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
new file mode 100755
index 0000000..60bfaa4
--- /dev/null
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
@@ -0,0 +1,39 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.adapters.HttpFacade;
+
+import javax.security.cert.X509Certificate;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletHttpFacade extends UndertowHttpFacade {
+    protected HttpServletRequest request;
+    protected HttpServletResponse response;
+
+    public ServletHttpFacade(HttpServerExchange exchange) {
+        super(exchange);
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        request = (HttpServletRequest)servletRequestContext.getServletRequest();
+    }
+
+    protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
+        @Override
+        public String getFirstParam(String param) {
+            return request.getParameter(param);
+        }
+
+    }
+
+    @Override
+    public Request getRequest() {
+        return new RequestFacade();
+    }
+}
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index d2c4b44..1abb86c 100755
--- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -21,7 +21,7 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public abstract class UndertowHttpFacade implements HttpFacade {
+public class UndertowHttpFacade implements HttpFacade {
     protected HttpServerExchange exchange;
     protected RequestFacade requestFacade = new RequestFacade();
     protected ResponseFacade responseFacade = new ResponseFacade();
@@ -67,6 +67,11 @@ public abstract class UndertowHttpFacade implements HttpFacade {
         }
 
         @Override
+        public String getFirstParam(String param) {
+            throw new RuntimeException("Not implemented yet");
+        }
+
+        @Override
         public String getQueryParamValue(String param) {
             Map<String,Deque<String>> queryParameters = exchange.getQueryParameters();
             if (queryParameters == null) return null;

pom.xml 5(+5 -0)

diff --git a/pom.xml b/pom.xml
index a35d56f..861f0f1 100755
--- a/pom.xml
+++ b/pom.xml
@@ -987,6 +987,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-saml-adapter-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-services</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
new file mode 100755
index 0000000..d5f86d7
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
@@ -0,0 +1,86 @@
+package org.keycloak.adapters.saml;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.w3c.dom.Document;
+
+import java.security.KeyPair;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InitiateLogin implements AuthChallenge {
+    protected static Logger log = Logger.getLogger(InitiateLogin.class);
+
+    protected SamlDeployment deployment;
+    protected SamlSessionStore sessionStore;
+
+    public InitiateLogin(SamlDeployment deployment, SamlSessionStore sessionStore) {
+        this.deployment = deployment;
+        this.sessionStore = sessionStore;
+    }
+
+    @Override
+    public boolean errorPage() {
+        return true;
+    }
+
+    @Override
+    public boolean challenge(HttpFacade httpFacade) {
+        try {
+            String issuerURL = deployment.getIssuer();
+            String actionUrl = deployment.getSingleSignOnServiceUrl();
+            String destinationUrl = actionUrl;
+            String nameIDPolicyFormat = deployment.getNameIDPolicyFormat();
+
+            if (nameIDPolicyFormat == null) {
+                nameIDPolicyFormat =  JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+            }
+
+            String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
+
+            if (deployment.getResponseBinding() == SamlDeployment.Binding.POST) {
+                protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
+            }
+
+            SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
+                    .assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl())
+                    .destination(destinationUrl)
+                    .issuer(issuerURL)
+                    .forceAuthn(deployment.isForceAuthentication())
+                    .protocolBinding(protocolBinding)
+                    .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
+            BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+
+            if (deployment.isRequestsSigned()) {
+
+
+                KeyPair keypair = deployment.getSigningKeyPair();
+                if (keypair == null) {
+                    throw new RuntimeException("Signing keys not configured");
+                }
+                if (deployment.getSignatureCanonicalizationMethod() != null) {
+                    binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+                }
+
+                binding.signWith(keypair);
+                binding.signDocument();
+            }
+            sessionStore.saveRequest();
+
+            Document document = authnRequestBuilder.toDocument();
+            SamlDeployment.Binding samlBinding = deployment.getRequestBinding();
+            SamlUtil.sendSaml(httpFacade, actionUrl, binding, document, samlBinding);
+        } catch (Exception e) {
+            throw new RuntimeException("Could not create authentication request.", e);
+        }
+        return true;
+    }
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
index eb5db0f..8c393bd 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -1,25 +1,430 @@
 package org.keycloak.adapters.saml;
 
 import org.jboss.logging.Logger;
+import org.keycloak.VerificationException;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.HttpFacade;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
+import org.keycloak.dom.saml.v2.assertion.SubjectType;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
+import org.keycloak.util.KeycloakUriBuilder;
+import org.keycloak.util.MultivaluedHashMap;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SamlAuthenticator {
+public abstract class SamlAuthenticator {
     protected static Logger log = Logger.getLogger(SamlAuthenticator.class);
 
     protected HttpFacade facade;
     protected AuthChallenge challenge;
+    protected SamlDeployment deployment;
+    protected SamlSessionStore sessionStore;
+
+    public SamlAuthenticator(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        this.facade = facade;
+        this.deployment = deployment;
+        this.sessionStore = sessionStore;
+    }
 
     public AuthChallenge getChallenge() {
         return challenge;
     }
 
     public AuthOutcome authenticate() {
-        return null;
+
+
+        String samlRequest = facade.getRequest().getFirstParam(GeneralConstants.SAML_REQUEST_KEY);
+        String samlResponse = facade.getRequest().getFirstParam(GeneralConstants.SAML_RESPONSE_KEY);
+        String relayState = facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE);
+        boolean globalLogout = "true".equals(facade.getRequest().getQueryParamValue("GLO"));
+        if (samlRequest != null) {
+            return handleSamlRequest(samlRequest, relayState);
+        } else if (samlResponse != null) {
+            return handleSamlResponse(samlResponse, relayState);
+        }  else if (sessionStore.isLoggedIn()) {
+            if (globalLogout) {
+                return globalLogout();
+            }
+            if (verifySSL()) return AuthOutcome.FAILED;
+            log.debug("AUTHENTICATED: was cached");
+            return AuthOutcome.AUTHENTICATED;
+        }
+        return initiateLogin();
+    }
+
+    protected AuthOutcome globalLogout() {
+        SamlSession account = sessionStore.getAccount();
+        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
+                .assertionExpiration(30)
+                .issuer(deployment.getIssuer())
+                .sessionIndex(account.getSessionIndex())
+                .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
+                .destination(deployment.getSingleLogoutServiceUrl());
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+        if (deployment.isRequestsSigned()) {
+            binding.signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+        }
+
+        binding.relayState("logout");
+
+        try {
+            SamlUtil.sendSaml(facade, deployment.getSingleLogoutServiceUrl(), binding, logoutBuilder.buildDocument(), deployment.getRequestBinding());
+        } catch (ProcessingException e) {
+            throw new RuntimeException(e);
+        } catch (ConfigurationException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } catch (ParsingException e) {
+            throw new RuntimeException(e);
+        }
+        return AuthOutcome.NOT_ATTEMPTED;
+    }
+
+    protected AuthOutcome handleSamlRequest(String samlRequest, String relayState) {
+        SAMLDocumentHolder holder = null;
+        boolean postBinding = false;
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+        } else {
+            postBinding = true;
+            holder = SAMLRequestParser.parseRequestPostBinding(samlRequest);
+        }
+        RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
+        if (!facade.getRequest().getURI().toString().equals(requestAbstractType.getDestination())) {
+            throw new RuntimeException("destination not equal to request");
+        }
+        validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY);
+
+        if (requestAbstractType instanceof LogoutRequestType) {
+            LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
+            return logoutRequest(logout, relayState);
+
+        } else {
+            throw new RuntimeException("unknown SAML request type");
+        }
+    }
+
+    protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+        if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
+            sessionStore.logoutByPrincipal(request.getNameID().getValue());
+        }  else {
+            sessionStore.logoutBySsoId(request.getSessionIndex());
+        }
+
+        String issuerURL = deployment.getIssuer();
+        SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+        builder.logoutRequestID(request.getID());
+        builder.destination(deployment.getSingleLogoutServiceUrl());
+        builder.issuer(issuerURL);
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
+        if (deployment.isRequestsSigned()) {
+            binding.signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+        }
+
+
+        try {
+            SamlUtil.sendSaml(facade, deployment.getSingleLogoutServiceUrl(), binding, builder.buildDocument(),
+                    deployment.getResponseBinding());
+        } catch (ConfigurationException e) {
+            throw new RuntimeException(e);
+        } catch (ProcessingException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return AuthOutcome.NOT_ATTEMPTED;
+
+    }
+
+
+    protected AuthOutcome handleSamlResponse(String samlResponse, String relayState) {
+        SAMLDocumentHolder holder = null;
+        boolean postBinding = false;
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            holder = extractRedirectBindingResponse(samlResponse);
+        } else {
+            postBinding = true;
+            holder = extractPostBindingResponse(samlResponse);
+        }
+        StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+        // validate destination
+        if (!facade.getRequest().getURI().toString().equals(statusResponse.getDestination())) {
+            throw new RuntimeException("destination not equal to request");
+        }
+        validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+        if (statusResponse instanceof ResponseType) {
+            return handleLoginResponse((ResponseType)statusResponse);
+
+        } else {
+            // todo need to check that it is actually a LogoutResponse
+            return handleLogoutResponse(holder, statusResponse, relayState);
+        }
+
+    }
+
+    private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) {
+        if (deployment.isValidateSignatures()) {
+            try {
+                if (postBinding) {
+                    verifyPostBindingSignature(holder.getSamlDocument(), deployment.getSignatureValidationKey());
+                } else {
+                    verifyRedirectBindingSignature(deployment.getSignatureValidationKey(), paramKey);
+                }
+            } catch (VerificationException e) {
+                log.error("validation failed", e);
+                throw new RuntimeException("invalid document signature");
+            }
+        }
+    }
+
+    protected AuthOutcome handleLoginResponse(ResponseType responseType)  {
+        AssertionType assertion = null;
+        try {
+            assertion = AssertionUtil.getAssertion(responseType, deployment.getAssertionDecryptionKey());
+            if (AssertionUtil.hasExpired(assertion)) {
+                return initiateLogin();
+            }
+        } catch (ParsingException e) {
+            throw new RuntimeException(e);
+        } catch (ProcessingException e) {
+            throw new RuntimeException(e);
+        } catch (ConfigurationException e) {
+            throw new RuntimeException(e);
+        }
+
+        SubjectType subject = assertion.getSubject();
+        SubjectType.STSubType subType = subject.getSubType();
+        NameIDType subjectNameID = (NameIDType) subType.getBaseID();
+        String principalName = subjectNameID.getValue();
+
+        final Set<String> roles = new HashSet<>();
+        MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+        MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+
+        Set<StatementAbstractType> statements = assertion.getStatements();
+        for (StatementAbstractType statement : statements) {
+            if (statement instanceof AttributeStatementType) {
+                AttributeStatementType attributeStatement = (AttributeStatementType) statement;
+                List<AttributeStatementType.ASTChoiceType> attList = attributeStatement.getAttributes();
+                for (AttributeStatementType.ASTChoiceType obj : attList) {
+                    AttributeType attr = obj.getAttribute();
+                    if (isRole(attr)) {
+                        List<Object> attributeValues = attr.getAttributeValue();
+                        if (attributeValues != null) {
+                            for (Object attrValue : attributeValues) {
+                                String role = getAttributeValue(attrValue);
+                                roles.add(role);
+                            }
+                        }
+                    } else {
+                        List<Object> attributeValues = attr.getAttributeValue();
+                        if (attributeValues != null) {
+                            for (Object attrValue : attributeValues) {
+                                String value = getAttributeValue(attrValue);
+                                if (attr.getName() != null) {
+                                    attributes.add(attr.getName(), value);
+                                }
+                                if (attr.getFriendlyName() != null) {
+                                    attributes.add(attr.getFriendlyName(), value);
+                                }
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+        if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE_NAME) {
+            if (deployment.getPrincipalAttributeName() != null) {
+                String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
+                if (attribute != null) principalName = attribute;
+            }
+        } else   if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_FRIENDLY_ATTRIBUTE_NAME) {
+            if (deployment.getPrincipalAttributeName() != null) {
+                String attribute = friendlyAttributes.getFirst(deployment.getPrincipalAttributeName());
+                if (attribute != null) principalName = attribute;
+            }
+        }
+
+        AuthnStatementType authn = null;
+        for (Object statement : assertion.getStatements()) {
+            if (statement instanceof AuthnStatementType) {
+                authn = (AuthnStatementType)statement;
+                break;
+            }
+        }
+
+
+        final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, subjectNameID.getFormat().toString(), attributes, friendlyAttributes);
+        String index = authn == null ? null : authn.getSessionIndex();
+        final String sessionIndex = index;
+        SamlSession account = new SamlSession() {
+            @Override
+            public SamlPrincipal getPrincipal() {
+                return principal;
+            }
+
+            @Override
+            public Set<String> getRoles() {
+                return roles;
+            }
+
+            @Override
+            public String getSessionIndex() {
+                return sessionIndex;
+            }
+        };
+        sessionStore.saveAccount(account);
+        completeAuthentication(account);
+
+        // redirect to original request, it will be restored
+        facade.getResponse().setHeader("Location", sessionStore.getRedirectUri());
+        facade.getResponse().setStatus(302);
+        facade.getResponse().end();
+
+
+        return AuthOutcome.AUTHENTICATED;
+    }
+
+    protected abstract void completeAuthentication(SamlSession account);
+
+    private String getAttributeValue(Object attrValue) {
+        String value;
+        if (attrValue instanceof String) {
+            value = (String)attrValue;
+        } else if (attrValue instanceof Node) {
+            Node roleNode = (Node) attrValue;
+            value = roleNode.getFirstChild().getNodeValue();
+        } else if (attrValue instanceof NameIDType) {
+            NameIDType nameIdType = (NameIDType) attrValue;
+            value = nameIdType.getValue();
+        } else
+            throw new RuntimeException("Unknown attribute value type: " + attrValue.getClass().getName());
+        return value;
+    }
+
+    protected boolean isRole(AttributeType attribute) {
+        return deployment.getRoleAttributeNames().contains(attribute.getName()) || deployment.getRoleAttributeFriendlyNames().contains(attribute.getFriendlyName());
+    }
+
+    protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
+        boolean loggedIn = sessionStore.isLoggedIn();
+        if (!loggedIn || !"logout".equals(relayState)) {
+            return AuthOutcome.NOT_ATTEMPTED;
+        }
+        sessionStore.logoutAccount();
+        return AuthOutcome.LOGGED_OUT;
+    }
+
+    protected SAMLDocumentHolder extractRedirectBindingResponse(String response) {
+        return SAMLRequestParser.parseRequestRedirectBinding(response);
+    }
+    protected SAMLDocumentHolder extractPostBindingResponse(String response) {
+        byte[] samlBytes = PostBindingUtil.base64Decode(response);
+        String xml = new String(samlBytes);
+        return SAMLRequestParser.parseResponseDocument(samlBytes);
+    }
+
+
+
+    protected AuthOutcome initiateLogin() {
+        challenge = new InitiateLogin(deployment, sessionStore);
+        return AuthOutcome.NOT_ATTEMPTED;
+    }
+
+    protected boolean verifySSL() {
+        if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
+            log.warn("SSL is required to authenticate");
+            return true;
+        }
+        return false;
     }
+
+    public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
+        SAML2Signature saml2Signature = new SAML2Signature();
+        try {
+            if (!saml2Signature.validate(document, publicKey)) {
+                throw new VerificationException("Invalid signature on document");
+            }
+        } catch (ProcessingException e) {
+            throw new VerificationException("Error validating signature", e);
+        }
+    }
+
+    public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
+        String request = facade.getRequest().getQueryParamValue(paramKey);
+        String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+        String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
+        String decodedAlgorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+
+        if (request == null) throw new VerificationException("SAM was null");
+        if (algorithm == null) throw new VerificationException("SigAlg was null");
+        if (signature == null) throw new VerificationException("Signature was null");
+
+        // Shibboleth doesn't sign the document for redirect binding.
+        // todo maybe a flag?
+
+        String relayState = facade.getRequest().getQueryParamValue(GeneralConstants.RELAY_STATE);
+        KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("/")
+                .queryParam(paramKey, request);
+        if (relayState != null) {
+            builder.queryParam(GeneralConstants.RELAY_STATE, relayState);
+        }
+        builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
+        String rawQuery = builder.build().getRawQuery();
+
+        try {
+            byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
+
+            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
+            Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
+            validator.initVerify(publicKey);
+            validator.update(rawQuery.getBytes("UTF-8"));
+            if (!validator.verify(decodedSignature)) {
+                throw new VerificationException("Invalid query param signature");
+            }
+        } catch (Exception e) {
+            throw new VerificationException(e);
+        }
+    }
+
+
+
 }
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
new file mode 100755
index 0000000..1de9d72
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -0,0 +1,50 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.enums.SslRequired;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlDeployment {
+    enum Binding {
+        POST,
+        REDIRECT
+    }
+
+    public boolean isConfigured();
+    SslRequired getSslRequired();
+    String getSingleSignOnServiceUrl();
+    String getSingleLogoutServiceUrl();
+    String getIssuer();
+    String getNameIDPolicyFormat();
+    String getAssertionConsumerServiceUrl();
+    Binding getRequestBinding();
+    Binding getResponseBinding();
+    KeyPair getSigningKeyPair();
+    String getSignatureCanonicalizationMethod();
+    boolean isForceAuthentication();
+    boolean isRequestsSigned();
+
+    boolean isValidateSignatures();
+    PublicKey getSignatureValidationKey();
+    PrivateKey getAssertionDecryptionKey();
+
+    Set<String> getRoleAttributeNames();
+    Set<String> getRoleAttributeFriendlyNames();
+
+    enum PrincipalNamePolicy {
+        FROM_NAME_ID,
+        FROM_ATTRIBUTE_NAME,
+        FROM_FRIENDLY_ATTRIBUTE_NAME
+    }
+    PrincipalNamePolicy getPrincipalNamePolicy();
+    String getPrincipalAttributeName();
+
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
new file mode 100755
index 0000000..b8ea94d
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlDeploymentContext {
+    public SamlDeployment resolveDeployment(HttpFacade facade) {
+        return null;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
new file mode 100755
index 0000000..f336c92
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
@@ -0,0 +1,84 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.util.MultivaluedHashMap;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlPrincipal implements Serializable, Principal {
+    private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+    private MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+    private String name;
+    private String samlSubject;
+    private String nameIDFormat;
+
+    public SamlPrincipal(String name, String samlSubject, String nameIDFormat, MultivaluedHashMap<String, String> attributes, MultivaluedHashMap<String, String> friendlyAttributes) {
+        this.name = name;
+        this.attributes = attributes;
+        this.friendlyAttributes = friendlyAttributes;
+        this.samlSubject = samlSubject;
+        this.nameIDFormat = nameIDFormat;
+    }
+
+    public SamlPrincipal() {
+    }
+
+    public String getSamlSubject() {
+        return samlSubject;
+    }
+
+    public String getNameIDFormat() {
+        return nameIDFormat;
+    }
+
+    @Override
+    public String getName() {
+        return null;
+    }
+
+
+    public List<String> getAttributes(String name) {
+        List<String> list = attributes.get(name);
+        if (list != null) {
+            return Collections.unmodifiableList(list);
+        } else {
+            return Collections.emptyList();
+        }
+
+    }
+    public List<String> getFriendlyAttributes(String friendlyName) {
+        List<String> list = friendlyAttributes.get(name);
+        if (list != null) {
+            return Collections.unmodifiableList(list);
+        } else {
+            return Collections.emptyList();
+        }
+
+    }
+
+    public String getAttribute(String name) {
+        return attributes.getFirst(name);
+    }
+
+    public String getFriendlyAttribute(String friendlyName) {
+        return friendlyAttributes.getFirst(friendlyName);
+    }
+
+    public Set<String> getAttributeNames() {
+        return Collections.unmodifiableSet(attributes.keySet());
+
+    }
+
+    public Set<String> getFriendlyNames() {
+        return Collections.unmodifiableSet(friendlyAttributes.keySet());
+
+    }
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
new file mode 100755
index 0000000..b3a1833
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
@@ -0,0 +1,15 @@
+package org.keycloak.adapters.saml;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlSession extends Serializable {
+    SamlPrincipal getPrincipal();
+    Set<String> getRoles();
+    String getSessionIndex();
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
new file mode 100755
index 0000000..6b6a83a
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
@@ -0,0 +1,20 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.AdapterSessionStore;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlSessionStore extends AdapterSessionStore {
+    boolean isLoggedIn();
+    SamlSession getAccount();
+    void saveAccount(SamlSession account);
+    String getRedirectUri();
+    void logoutAccount();
+    void logoutByPrincipal(String principal);
+    void logoutBySsoId(List<String> ssoIds);
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
new file mode 100755
index 0000000..b0981db
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
@@ -0,0 +1,31 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlUtil {
+    public static void sendSaml(HttpFacade httpFacade, String actionUrl, BaseSAML2BindingBuilder binding, Document document, SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
+        if (samlBinding == SamlDeployment.Binding.POST) {
+            String html = binding.postBinding(document).getHtmlRequest(actionUrl);
+            httpFacade.getResponse().setStatus(200);
+            httpFacade.getResponse().setHeader("Content-Type", "text/html");
+            httpFacade.getResponse().setHeader("Pragma", "no-cache");
+            httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
+            httpFacade.getResponse().getOutputStream().write(html.getBytes());
+            httpFacade.getResponse().end();
+        } else {
+            String uri = binding.redirectBinding(document).requestURI(actionUrl).toString();
+            httpFacade.getResponse().setStatus(302);
+            httpFacade.getResponse().setHeader("Location", uri);
+        }
+    }
+}
diff --git a/saml/client-adapter/pom.xml b/saml/client-adapter/pom.xml
index 5242ff6..6ca4df9 100755
--- a/saml/client-adapter/pom.xml
+++ b/saml/client-adapter/pom.xml
@@ -15,5 +15,6 @@
 
     <modules>
         <module>core</module>
+        <module>undertow</module>
     </modules>
 </project>
diff --git a/saml/client-adapter/undertow/pom.xml b/saml/client-adapter/undertow/pom.xml
new file mode 100755
index 0000000..6049517
--- /dev/null
+++ b/saml/client-adapter/undertow/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.5.0.Final-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-undertow-saml-adapter</artifactId>
+    <name>Keycloak Undertow SAML Adapter</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-adapter-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-servlet</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
new file mode 100755
index 0000000..6a21c5e
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.NotificationReceiver;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.api.SecurityNotification;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
+import io.undertow.util.StatusCodes;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
+    public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
+    protected SamlDeploymentContext deploymentContext;
+    protected UndertowUserSessionManagement sessionManagement;
+    protected String logoutPage;
+    protected String errorPage;
+
+    public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+                                String logoutPage,
+                                String errorPage) {
+        this.deploymentContext = deploymentContext;
+        this.sessionManagement = sessionManagement;
+        this.errorPage = errorPage;
+        this.logoutPage = logoutPage;
+    }
+
+    @Override
+    public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+        AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+        if (challenge != null) {
+            if (challenge.errorPage() && errorPage != null) {
+                Integer code = servePage(exchange, errorPage);
+                return new ChallengeResult(true, code);
+            }
+            UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+            if (challenge.challenge(facade)) {
+                return new ChallengeResult(true, exchange.getResponseCode());
+            }
+        }
+        return new ChallengeResult(false);
+    }
+
+    protected Integer servePage(final HttpServerExchange exchange, final String location) {
+        sendRedirect(exchange, location);
+        return StatusCodes.TEMPORARY_REDIRECT;
+    }
+
+    static void sendRedirect(final HttpServerExchange exchange, final String location) {
+        // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
+        String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
+        exchange.getResponseHeaders().put(Headers.LOCATION, loc);
+    }
+
+
+
+    protected void registerNotifications(final SecurityContext securityContext) {
+
+        final NotificationReceiver logoutReceiver = new NotificationReceiver() {
+            @Override
+            public void handleNotification(SecurityNotification notification) {
+                if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
+
+                HttpServerExchange exchange = notification.getExchange();
+                UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+                SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+                SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+                sessionStore.logoutAccount();
+            }
+        };
+
+        securityContext.registerNotificationReceiver(logoutReceiver);
+    }
+
+    /**
+     * Call this inside your authenticate method.
+     */
+    public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+        if (!deployment.isConfigured()) {
+            return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+        }
+        SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+        UndertowSamlAuthenticator authenticator = new UndertowSamlAuthenticator(securityContext, facade,
+                deploymentContext.resolveDeployment(facade), sessionStore);
+        AuthOutcome outcome = authenticator.authenticate();
+        if (outcome == AuthOutcome.AUTHENTICATED) {
+            registerNotifications(securityContext);
+            return AuthenticationMechanismOutcome.AUTHENTICATED;
+        }
+        if (outcome == AuthOutcome.LOGGED_OUT) {
+            securityContext.logout();
+            if (logoutPage != null) {
+                sendRedirect(exchange, logoutPage);
+                exchange.setResponseCode(302);
+                exchange.endExchange();
+            }
+            return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+        }
+        AuthChallenge challenge = authenticator.getChallenge();
+        if (challenge != null) {
+            exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, challenge);
+        }
+
+        if (outcome == AuthOutcome.FAILED) {
+            return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+        }
+        return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+    }
+
+    protected abstract SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext);
+}
\ No newline at end of file
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
new file mode 100755
index 0000000..ec2444a
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlAuthMech extends AbstractSamlAuthMech {
+    public ServletSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+                               String logoutPage, String errorPage) {
+        super(deploymentContext, sessionManagement, logoutPage, errorPage);
+    }
+
+    @Override
+    protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
+        return new ServletSamlSessionStore(exchange, sessionManagement, securityContext);
+    }
+}
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
new file mode 100755
index 0000000..696a29c
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -0,0 +1,140 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.servlet.spec.HttpSessionImpl;
+import io.undertow.servlet.util.SavedRequest;
+import io.undertow.util.Sessions;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.util.KeycloakUriBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.security.Principal;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlSessionStore implements SamlSessionStore {
+    protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+    public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+
+    private final HttpServerExchange exchange;
+    private final UndertowUserSessionManagement sessionManagement;
+    private final SecurityContext securityContext;
+
+    public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
+                                   SecurityContext securityContext) {
+        this.exchange = exchange;
+        this.sessionManagement = sessionManagement;
+        this.securityContext = securityContext;
+    }
+
+    @Override
+    public void logoutAccount() {
+        HttpSession session = getSession(false);
+        if (session != null) {
+            session.removeAttribute(SamlSession.class.getName());
+            session.removeAttribute(SAML_REDIRECT_URI);
+        }
+    }
+
+    @Override
+    public void logoutByPrincipal(String principal) {
+
+    }
+
+    @Override
+    public void logoutBySsoId(List<String> ssoIds) {
+
+    }
+
+    @Override
+    public boolean isLoggedIn() {
+        HttpSession session = getSession(false);
+        if (session == null) {
+            log.debug("session was null, returning null");
+            return false;
+        }
+        final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+        if (samlSession == null) {
+            log.debug("SamlSession was not in session, returning null");
+            return false;
+        }
+
+        Account undertowAccount = new Account() {
+            @Override
+            public Principal getPrincipal() {
+                return samlSession.getPrincipal();
+            }
+
+            @Override
+            public Set<String> getRoles() {
+                return samlSession.getRoles();
+            }
+        };
+        securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+        restoreRequest();
+        return true;
+    }
+
+    @Override
+    public void saveAccount(SamlSession account) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpSession session = getSession(true);
+        session.setAttribute(SamlSession.class.getName(), account);
+        sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+
+    }
+
+    @Override
+    public SamlSession getAccount() {
+        HttpSession session = getSession(true);
+        return (SamlSession)session.getAttribute(SamlSession.class.getName());
+    }
+
+    @Override
+    public String getRedirectUri() {
+        final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+        return (String)session.getAttribute(SAML_REDIRECT_URI);
+    }
+
+    @Override
+    public void saveRequest() {
+        SavedRequest.trySaveRequest(exchange);
+        final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+        KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
+                .replaceQuery(exchange.getQueryString());
+        if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+        String uri = uriBuilder.build().toString();
+
+        session.setAttribute(SAML_REDIRECT_URI, uri);
+
+    }
+
+    @Override
+    public boolean restoreRequest() {
+        HttpSession session = getSession(false);
+        if (session == null) return false;
+        SavedRequest.tryRestoreRequest(exchange, session);
+        session.removeAttribute(SAML_REDIRECT_URI);
+        return false;
+    }
+
+    protected HttpSession getSession(boolean create) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+        return req.getSession(create);
+    }
+}
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
new file mode 100755
index 0000000..dfb2843
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowSamlAuthenticator extends SamlAuthenticator {
+    protected SecurityContext securityContext;
+
+    public UndertowSamlAuthenticator(SecurityContext securityContext, HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+        super(facade, deployment, sessionStore);
+        this.securityContext = securityContext;
+    }
+
+    @Override
+    protected void completeAuthentication(final SamlSession samlSession) {
+        Account undertowAccount = new Account() {
+            @Override
+            public Principal getPrincipal() {
+                return samlSession.getPrincipal();
+            }
+
+            @Override
+            public Set<String> getRoles() {
+                return samlSession.getRoles();
+            }
+        };
+        securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+
+    }
+}
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index a39adf3..63b5f5e 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -164,7 +164,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
         public Document getDocument() {
             return document;
         }
-        public URI responseUri(String redirectUri, boolean asRequest) throws ConfigurationException, ProcessingException, IOException {
+        public URI generateURI(String redirectUri, boolean asRequest) throws ConfigurationException, ProcessingException, IOException {
             String samlParameterName = GeneralConstants.SAML_RESPONSE_KEY;
 
             if (asRequest) {
@@ -173,6 +173,23 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
 
             return builder.generateRedirectUri(samlParameterName, redirectUri, document);
         }
+
+        public URI requestURI(String actionUrl)  throws ConfigurationException, ProcessingException, IOException {
+            return builder.generateRedirectUri(GeneralConstants.SAML_REQUEST_KEY, actionUrl, document);
+        }
+        public URI responseURI(String actionUrl)  throws ConfigurationException, ProcessingException, IOException {
+            return builder.generateRedirectUri(GeneralConstants.SAML_RESPONSE_KEY, actionUrl, document);
+        }
+    }
+
+    public BaseRedirectBindingBuilder redirectBinding(Document document) throws ProcessingException {
+        return new BaseRedirectBindingBuilder(this, document);
+
+    }
+
+    public BasePostBindingBuilder postBinding(Document document) throws ProcessingException {
+        return new BasePostBindingBuilder(this, document);
+
     }
 
 
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 0f6f5ed..eaf7d8c 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -21,15 +21,22 @@
  */
 package org.keycloak.saml.processing.core.saml.v2.util;
 
+import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
 import org.keycloak.saml.common.ErrorCodes;
 import org.keycloak.saml.common.PicketLinkLogger;
 import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.common.exceptions.fed.IssueInstantMissingException;
 import org.keycloak.saml.common.util.DocumentUtil;
+import org.keycloak.saml.common.util.StaxParserUtil;
 import org.keycloak.saml.common.util.StaxUtil;
+import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
 import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
 import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
 import org.keycloak.dom.saml.v1.assertion.SAML11AssertionType;
 import org.keycloak.dom.saml.v1.assertion.SAML11AttributeStatementType;
@@ -45,13 +52,17 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
 import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
+import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
+import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.namespace.QName;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.List;
@@ -510,4 +521,48 @@ public class AssertionUtil {
         }
         return roles;
     }
+
+    public static AssertionType getAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
+        List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
+
+        if (assertions.isEmpty()) {
+            throw new ProcessingException("No assertion from response.");
+        }
+
+        ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
+        EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
+
+        if (encryptedAssertion != null) {
+            decryptAssertion(responseType, privateKey);
+
+        }
+        return responseType.getAssertions().get(0).getAssertion();
+    }
+
+    public static ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
+        SAML2Response saml2Response = new SAML2Response();
+
+        Document doc = saml2Response.convert(responseType);
+        Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+
+        if (enc == null) {
+            throw new ProcessingException("No encrypted assertion found.");
+        }
+
+        String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
+        Document newDoc = DocumentUtil.createDocument();
+        Node importedNode = newDoc.importNode(enc, true);
+        newDoc.appendChild(importedNode);
+
+        Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
+        SAMLParser parser = new SAMLParser();
+
+        JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
+        AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
+                .getNodeAsStream(decryptedDocumentElement)));
+
+        responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
+
+        return responseType;
+    }
 }
\ No newline at end of file
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
index 36e5804..3afe813 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
@@ -29,8 +29,6 @@ public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2
         protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
             String str = builder.buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
 
-            CacheControl cacheControl = new CacheControl();
-            cacheControl.setNoCache(true);
             return Response.ok(str, MediaType.TEXT_HTML_TYPE)
                     .header("Pragma", "no-cache")
                     .header("Cache-Control", "no-cache, no-store").build();
@@ -53,7 +51,7 @@ public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2
         }
 
         private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
-            URI uri = responseUri(redirectUri, asRequest);
+            URI uri = generateURI(redirectUri, asRequest);
             if (logger.isDebugEnabled()) logger.trace("redirect-binding uri: " + uri.toString());
             CacheControl cacheControl = new CacheControl();
             cacheControl.setNoCache(true);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index bc18f3d..d9d937e 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -14,7 +14,6 @@ import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.security.PublicKey;
-import java.security.SecureRandom;
 import java.security.Signature;
 import java.security.cert.Certificate;
 
@@ -64,23 +63,23 @@ public class SamlProtocolUtils {
         return cert.getPublicKey();
     }
 
-    public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation) throws VerificationException {
+    public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
         MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
-        String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
+        String request = encodedParams.getFirst(paramKey);
         String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
         String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
         String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
 
-        if (request == null) throw new VerificationException("SAMLRequest as null");
-        if (algorithm == null) throw new VerificationException("SigAlg as null");
-        if (signature == null) throw new VerificationException("Signature as null");
+        if (request == null) throw new VerificationException("SAM was null");
+        if (algorithm == null) throw new VerificationException("SigAlg was null");
+        if (signature == null) throw new VerificationException("Signature was null");
 
         // Shibboleth doesn't sign the document for redirect binding.
         // todo maybe a flag?
 
 
         UriBuilder builder = UriBuilder.fromPath("/")
-                .queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
+                .queryParam(paramKey, request);
         if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
             builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
         }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 0780e94..9521e0c 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -460,7 +460,7 @@ public class SamlService {
                 return;
             }
             PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
         }