keycloak-aplcache

Changes

pom.xml 30(+30 -0)

saml/pom.xml 1(+1 -0)

saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder2.java 372(+0 -372)

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 f15a23f..24b56bb 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;
@@ -24,31 +23,23 @@ import org.keycloak.events.EventType;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
-import org.keycloak.protocol.saml.SAMLRequestParser;
+import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+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;
@@ -61,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;
@@ -155,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() {
@@ -188,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);
@@ -247,17 +236,18 @@ public class SAMLEndpoint {
             builder.logoutRequestID(request.getID());
             builder.destination(config.getSingleLogoutServiceUrl());
             builder.issuer(issuerURL);
-            builder.relayState(relayState);
+            JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
+                        .relayState(relayState);
             if (config.isWantAuthnRequestsSigned()) {
-                builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+                binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
                         .signatureAlgorithm(provider.getSignatureAlgorithm())
                         .signDocument();
             }
             try {
                 if (config.isPostBindingResponse()) {
-                    return builder.postBinding().response();
+                    return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
                 } else {
-                    return builder.redirectBinding().response();
+                    return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
                 }
             } catch (ConfigurationException e) {
                 throw new RuntimeException(e);
@@ -275,7 +265,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();
@@ -335,22 +325,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);
@@ -364,7 +338,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);
@@ -407,43 +381,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());
         }
 
@@ -466,9 +411,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/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 52959f8..e28aa97 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -33,21 +33,18 @@ import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
-import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
-import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
-import org.keycloak.protocol.saml.SignatureAlgorithm;
+import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
-import org.keycloak.saml.common.exceptions.ConfigurationException;
-import org.keycloak.saml.common.exceptions.ParsingException;
-import org.keycloak.saml.common.exceptions.ProcessingException;
 
 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 java.io.IOException;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -93,7 +90,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                     .issuer(issuerURL)
                     .forceAuthn(getConfig().isForceAuthn())
                     .protocolBinding(protocolBinding)
-                    .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
+                    .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
+            JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                     .relayState(request.getState());
 
             if (getConfig().isWantAuthnRequestsSigned()) {
@@ -110,15 +108,15 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
 
                 KeyPair keypair = new KeyPair(publicKey, privateKey);
 
-                authnRequestBuilder.signWith(keypair);
-                authnRequestBuilder.signatureAlgorithm(getSignatureAlgorithm());
-                authnRequestBuilder.signDocument();
+                binding.signWith(keypair);
+                binding.signatureAlgorithm(getSignatureAlgorithm());
+                binding.signDocument();
             }
 
             if (getConfig().isPostBindingAuthnRequest()) {
-                return authnRequestBuilder.postBinding().request();
+                return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
             } else {
-                return authnRequestBuilder.redirectBinding().request();
+                return binding.redirectBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
             }
         } catch (Exception e) {
             throw new IdentityBrokerException("Could not create authentication request.", e);
@@ -155,9 +153,10 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
         if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
         SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
+        JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
         try {
             int status = SimpleHttp.doPost(singleLogoutServiceUrl)
-                    .param(GeneralConstants.SAML_REQUEST_KEY, logoutBuilder.postBinding().encoded())
+                    .param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
                     .param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
             boolean success = status >=200 && status < 400;
             if (!success) {
@@ -180,7 +179,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
        } else {
             try {
                 SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
-                return logoutBuilder.postBinding().request();
+                JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
+                return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
@@ -194,14 +194,19 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 .issuer(getEntityId(uriInfo, realm))
                 .sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
                 .userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
-                .destination(singleLogoutServiceUrl)
+                .destination(singleLogoutServiceUrl);
+        return logoutBuilder;
+    }
+
+    private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
+        JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                 .relayState(userSession.getId());
         if (getConfig().isWantAuthnRequestsSigned()) {
-            logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+            binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
                     .signatureAlgorithm(getSignatureAlgorithm())
                     .signDocument();
         }
-        return logoutBuilder;
+        return binding;
     }
 
     @Override
@@ -257,4 +262,5 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         }
         return SignatureAlgorithm.RSA_SHA256;
     }
+
 }
diff --git a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
index 59ae243..dba2c93 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
@@ -14,6 +14,7 @@
                 <include>org/bouncycastle/**</include>
                 <include>net/iharder/base64/**</include>
                 <include>org/keycloak/keycloak-core/**</include>
+                <include>org/keycloak/keycloak-adapter-spi/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
                 <include>org/keycloak/keycloak-as7-adapter/**</include>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml
index 9ab6fab..c3e0add 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml
@@ -53,6 +53,11 @@
 
         <!-- subsystems -->
 
+        <module-def name="org.keycloak.keycloak-adapter-spi">
+            <maven-resource group="org.keycloak" artifact="keycloak-adapter-spi"/>
+            <maven-resource group="org.keycloak" artifact="keycloak-tomcat-adapter-spi"/>
+        </module-def>
+
         <module-def name="org.keycloak.keycloak-adapter-core">
             <maven-resource group="org.keycloak" artifact="keycloak-adapter-core"/>
         </module-def>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
index 04724d4..1628f9b 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
@@ -23,6 +23,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
index 5e3e5c3..c2975ba 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -14,6 +14,7 @@
         <module name="org.apache.httpcomponents"/>
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="net.iharder.base64"/>
     </dependencies>
 
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
new file mode 100755
index 0000000..08d4d5e
--- /dev/null
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-adapter-spi">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+    <dependencies>
+        <module name="javax.api"/>
+        <module name="org.jboss.logging"/>
+        <module name="org.jboss.as.web"/>
+        <module name="javax.servlet.api"/>
+        <module name="org.apache.httpcomponents"/>
+        <module name="org.bouncycastle" />
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="net.iharder.base64"/>
+    </dependencies>
+
+</module>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-as7-adapter/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-as7-adapter/main/module.xml
index 463ff43..05053c1 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-as7-adapter/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-as7-adapter/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.jboss.as.security"/>
         <module name="org.jboss.as.web"/>
         <module name="org.picketbox"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
index beac07b..f1ee530 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -10,6 +10,7 @@
         <module name="javax.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
index bc48988..c906cc9 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
@@ -10,6 +10,7 @@
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
         <module name="org.apache.httpcomponents"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
index 0f6c462..5ea03d1 100755
--- a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
+++ b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
@@ -14,6 +14,7 @@
                 <include>org/bouncycastle/**</include>
                 <include>net/iharder/base64/**</include>
                 <include>org/keycloak/keycloak-core/**</include>
+                <include>org/keycloak/keycloak-adapter-spi/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
                 <include>org/keycloak/keycloak-as7-adapter/**</include>
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
index da4e127..0d134dc 100755
--- a/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
@@ -14,6 +14,7 @@
                 <include>net/iharder/base64/**</include>
                 <include>org/apache/httpcomponents/**</include>
                 <include>org/keycloak/keycloak-core/**</include>
+                <include>org/keycloak/keycloak-adapter-spi/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
                 <include>org/keycloak/keycloak-undertow-adapter/**</include>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/build.xml b/distribution/adapters/wf8-adapter/wf8-modules/build.xml
index 30e0254..1601598 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/build.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/build.xml
@@ -49,6 +49,11 @@
 
         <!-- subsystems -->
 
+        <module-def name="org.keycloak.keycloak-adapter-spi">
+            <maven-resource group="org.keycloak" artifact="keycloak-adapter-spi"/>
+            <maven-resource group="org.keycloak" artifact="keycloak-undertow-adapter-spi"/>
+        </module-def>
+
         <module-def name="org.keycloak.keycloak-adapter-core">
             <maven-resource group="org.keycloak" artifact="keycloak-adapter-core"/>
         </module-def>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
index e98c41c..e652ede 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
@@ -23,6 +23,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
index 1be1486..cfa8a0e 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.apache.httpcomponents" slot="4.3" />
         <module name="org.jboss.logging"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="net.iharder.base64"/>
     </dependencies>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
new file mode 100755
index 0000000..28c0543
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-adapter-spi">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+    <dependencies>
+        <module name="javax.api"/>
+        <module name="org.apache.httpcomponents" slot="4.3" />
+        <module name="javax.servlet.api"/>
+        <module name="org.jboss.logging"/>
+        <module name="org.jboss.xnio"/>
+        <module name="io.undertow.core"/>
+        <module name="io.undertow.servlet"/>
+        <module name="org.keycloak.keycloak-core"/>
+    </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
index beac07b..f1ee530 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -10,6 +10,7 @@
         <module name="javax.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
index 2d3876e..1e92a95 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
@@ -10,6 +10,7 @@
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
         <module name="org.apache.httpcomponents" slot="4.3"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
index 1772a22..c3f1775 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.jboss.xnio"/>
         <module name="io.undertow.core"/>
         <module name="io.undertow.servlet"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
index 2b0e537..c0786cf 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
@@ -19,6 +19,7 @@
         <module name="io.undertow.servlet"/>
         <module name="org.picketbox"/>
         <module name="org.keycloak.keycloak-undertow-adapter"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml b/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml
index 764b76d..e839021 100755
--- a/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml
+++ b/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml
@@ -14,6 +14,7 @@
                 <include>net/iharder/base64/**</include>
                 <include>org/keycloak/keycloak-core/**</include>
                 <include>org/keycloak/keycloak-adapter-core/**</include>
+                <include>org/keycloak/keycloak-adapter-spi/**</include>
                 <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
                 <include>org/keycloak/keycloak-undertow-adapter/**</include>
                 <include>org/keycloak/keycloak-wildfly-adapter/**</include>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/build.xml b/distribution/adapters/wf9-adapter/wf9-modules/build.xml
index 8c1d41a..ce67858 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/build.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/build.xml
@@ -49,6 +49,11 @@
 
         <!-- subsystems -->
 
+        <module-def name="org.keycloak.keycloak-adapter-spi">
+            <maven-resource group="org.keycloak" artifact="keycloak-adapter-spi"/>
+            <maven-resource group="org.keycloak" artifact="keycloak-undertow-adapter-spi"/>
+        </module-def>
+
         <module-def name="org.keycloak.keycloak-adapter-core">
             <maven-resource group="org.keycloak" artifact="keycloak-adapter-core"/>
         </module-def>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/pom.xml b/distribution/adapters/wf9-adapter/wf9-modules/pom.xml
index 9277184..270cc55 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/pom.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/pom.xml
@@ -23,6 +23,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
index 5e3e5c3..6c25a03 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.apache.httpcomponents"/>
         <module name="org.jboss.logging"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="net.iharder.base64"/>
     </dependencies>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
new file mode 100755
index 0000000..850c46a
--- /dev/null
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-adapter-spi">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+    <dependencies>
+        <module name="javax.api"/>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.apache.httpcomponents"/>
+        <module name="javax.servlet.api"/>
+        <module name="org.jboss.logging"/>
+        <module name="org.jboss.xnio"/>
+        <module name="io.undertow.core"/>
+        <module name="io.undertow.servlet"/>
+    </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
index beac07b..f1ee530 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -10,6 +10,7 @@
         <module name="javax.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
index bc48988..c906cc9 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
@@ -10,6 +10,7 @@
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
         <module name="org.apache.httpcomponents"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
index 4b3a4c9..4dd6f2d 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.jboss.xnio"/>
         <module name="io.undertow.core"/>
         <module name="io.undertow.servlet"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
index 3d65f2b..853ca8d 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
@@ -19,6 +19,7 @@
         <module name="io.undertow.servlet"/>
         <module name="org.picketbox"/>
         <module name="org.keycloak.keycloak-undertow-adapter"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
old mode 100644
new mode 100755
index 180d47b..37a4c6c
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -46,6 +46,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-undertow-adapter</artifactId>
         </dependency>
         <dependency>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml
old mode 100644
new mode 100755
index 6407abc..0c854b9
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.apache.httpcomponents" />
         <module name="org.jboss.logging"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="net.iharder.base64"/>
     </dependencies>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-spi/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-spi/main/module.xml
new file mode 100755
index 0000000..ad826da
--- /dev/null
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-spi/main/module.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-adapter-spi">
+    <resources>
+        <artifact name="${org.keycloak:keycloak-adapter-spi}"/>
+        <artifact name="${org.keycloak:keycloak-undertow-adapter-spi}"/>
+    </resources>
+    <dependencies>
+        <module name="javax.api"/>
+        <module name="org.jboss.logging"/>
+        <module name="org.jboss.xnio"/>
+        <module name="io.undertow.core"/>
+        <module name="io.undertow.servlet"/>
+        <module name="javax.servlet.api"/>
+        <module name="org.bouncycastle" />
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="net.iharder.base64"/>
+    </dependencies>
+
+</module>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
old mode 100644
new mode 100755
index 243a5b9..c836034
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -10,6 +10,7 @@
         <module name="javax.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-servlet-oauth-client/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
index 73fccad..fb39438 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-servlet-oauth-client/main/module.xml
@@ -10,6 +10,7 @@
         <module name="org.jboss.logging"/>
         <module name="org.picketbox"/>
         <module name="org.apache.httpcomponents"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml
old mode 100644
new mode 100755
index 75ac27e..b813b7f
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.jboss.xnio"/>
         <module name="io.undertow.core"/>
         <module name="io.undertow.servlet"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml
index 88eaafa..4610766 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml
@@ -19,6 +19,7 @@
         <module name="io.undertow.servlet"/>
         <module name="org.picketbox"/>
         <module name="org.keycloak.keycloak-undertow-adapter"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-adapter-core"/>
         <module name="org.keycloak.keycloak-core"/>
     </dependencies>
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
index b75c76d..ac316a5 100755
--- a/examples/multi-tenant/pom.xml
+++ b/examples/multi-tenant/pom.xml
@@ -39,6 +39,10 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
 
         <!-- Contains KeycloakPrincipal -->
         <dependency>
diff --git a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java
old mode 100644
new mode 100755
index 44f7e78..b722300
--- a/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java
+++ b/examples/multi-tenant/src/main/java/org/keycloak/example/multitenant/control/PathBasedKeycloakConfigResolver.java
@@ -17,14 +17,13 @@
 package org.keycloak.example.multitenant.control;
 
 import java.io.InputStream;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakConfigResolver;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
 
 /**
  *
@@ -35,7 +34,7 @@ public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
     private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
 
     @Override
-    public KeycloakDeployment resolve(HttpFacade.Request request) {
+    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
         String path = request.getURI();
         int multitenantIndex = path.indexOf("multitenant/");
         if (multitenantIndex == -1) {
diff --git a/integration/adapter-core/pom.xml b/integration/adapter-core/pom.xml
index 973003e..92308c4 100755
--- a/integration/adapter-core/pom.xml
+++ b/integration/adapter-core/pom.xml
@@ -40,6 +40,11 @@
         </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>
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/AuthenticatedActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
index 0a73404..f4eced9 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
@@ -23,9 +23,9 @@ import java.util.Set;
 public class AuthenticatedActionsHandler {
     private static final Logger log = Logger.getLogger(AuthenticatedActionsHandler.class);
     protected KeycloakDeployment deployment;
-    protected HttpFacade facade;
+    protected OIDCHttpFacade facade;
 
-    public AuthenticatedActionsHandler(KeycloakDeployment deployment, HttpFacade facade) {
+    public AuthenticatedActionsHandler(KeycloakDeployment deployment, OIDCHttpFacade facade) {
         this.deployment = deployment;
         this.facade = facade;
     }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index f5e145f..73d58bc 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -34,7 +34,7 @@ public class CookieTokenStore {
     }
 
     public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie(KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) {
-        HttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
+        OIDCHttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
         if (cookie == null) {
             log.debug("Not found adapter state cookie in current request");
             return null;
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 3189ab2..d077d7d 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
@@ -14,7 +14,6 @@ import org.keycloak.util.KeycloakUriBuilder;
 import org.keycloak.util.UriUtils;
 
 import java.io.IOException;
-import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
 
 
@@ -27,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;
@@ -37,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;
@@ -93,12 +92,12 @@ public class OAuthRequestAuthenticator {
         return facade.getRequest().isSecure();
     }
 
-    protected HttpFacade.Cookie getCookie(String cookieName) {
+    protected OIDCHttpFacade.Cookie getCookie(String cookieName) {
         return facade.getRequest().getCookie(cookieName);
     }
 
     protected String getCookieValue(String cookieName) {
-        HttpFacade.Cookie cookie = getCookie(cookieName);
+        OIDCHttpFacade.Cookie cookie = getCookie(cookieName);
         if (cookie == null) return null;
         return cookie.getValue();
     }
@@ -204,7 +203,7 @@ public class OAuthRequestAuthenticator {
     }
 
     protected AuthChallenge checkStateCookie() {
-        HttpFacade.Cookie stateCookie = getCookie(deployment.getStateCookieName());
+        OIDCHttpFacade.Cookie stateCookie = getCookie(deployment.getStateCookieName());
 
         if (stateCookie == null) {
             log.warn("No state cookie");
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCHttpFacade.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCHttpFacade.java
new file mode 100755
index 0000000..5b1cadc
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OIDCHttpFacade.java
@@ -0,0 +1,14 @@
+package org.keycloak.adapters;
+
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ * Bridge between core adapter and HTTP Engine
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface OIDCHttpFacade extends HttpFacade {
+
+    KeycloakSecurityContext getSecurityContext();
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
index 04cf79e..daf205a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -9,11 +9,11 @@ import org.keycloak.KeycloakPrincipal;
  */
 public abstract class RequestAuthenticator {
     protected static Logger log = Logger.getLogger(RequestAuthenticator.class);
-
     protected HttpFacade facade;
+    protected AuthChallenge challenge;
+
     protected KeycloakDeployment deployment;
     protected AdapterTokenStore tokenStore;
-    protected AuthChallenge challenge;
     protected int sslRedirectPort;
 
     public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
diff --git a/integration/adapter-spi/pom.xml b/integration/adapter-spi/pom.xml
new file mode 100755
index 0000000..1e18e76
--- /dev/null
+++ b/integration/adapter-spi/pom.xml
@@ -0,0 +1,93 @@
+<?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.6.0.Final-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-adapter-spi</artifactId>
+    <name>Keycloak Adapter SPI</name>
+    <description/>
+
+    <properties>
+        <keycloak.osgi.export>
+            org.keycloak.adapters.*
+        </keycloak.osgi.export>
+        <keycloak.osgi.import>
+            org.keycloak.*;version="${project.version}",
+            org.apache.http.*;version=${apache.httpcomponents.version},
+            org.apache.karaf.jaas.boot.principal;resolution:=optional,
+            org.apache.karaf.jaas.modules;resolution:=optional,
+            *;resolution:=optional
+        </keycloak.osgi.import>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+            <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>
+
+            <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <id>bundle-manifest</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <instructions>
+                        <Bundle-ClassPath>.</Bundle-ClassPath>
+                        <Bundle-Name>${project.name}</Bundle-Name>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>${keycloak.osgi.import}</Import-Package>
+                        <Export-Package>${keycloak.osgi.export}</Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
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/InMemorySessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
new file mode 100755
index 0000000..4470c15
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
@@ -0,0 +1,69 @@
+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 SessionIdMapper {
+    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) {
+        if (sso != null) {
+            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/SessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.java
new file mode 100755
index 0000000..646f71b
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.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 SessionIdMapper {
+    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/as7-eap6/as7-adapter/pom.xml b/integration/as7-eap6/as7-adapter/pom.xml
index e13542b..74c35f2 100755
--- a/integration/as7-eap6/as7-adapter/pom.xml
+++ b/integration/as7-eap6/as7-adapter/pom.xml
@@ -20,6 +20,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/integration/installed/pom.xml b/integration/installed/pom.xml
index 379f7b6..c107496 100755
--- a/integration/installed/pom.xml
+++ b/integration/installed/pom.xml
@@ -20,6 +20,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/integration/jaxrs-oauth-client/pom.xml b/integration/jaxrs-oauth-client/pom.xml
index 33630c8..90b00e2 100755
--- a/integration/jaxrs-oauth-client/pom.xml
+++ b/integration/jaxrs-oauth-client/pom.xml
@@ -33,6 +33,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
             <scope>provided</scope>
         </dependency>
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
old mode 100644
new mode 100755
index f29935d..490d42d
--- 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
@@ -11,13 +11,13 @@ import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.SecurityContext;
 
 import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.OIDCHttpFacade;
 import org.keycloak.util.HostUtils;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class JaxrsHttpFacade implements HttpFacade {
+public class JaxrsHttpFacade implements OIDCHttpFacade {
 
     protected final ContainerRequestContext requestContext;
     protected final SecurityContext securityContext;
@@ -31,7 +31,12 @@ public class JaxrsHttpFacade implements HttpFacade {
         this.securityContext = securityContext;
     }
 
-    protected class RequestFacade implements HttpFacade.Request {
+    protected class RequestFacade implements OIDCHttpFacade.Request {
+
+        @Override
+        public String getFirstParam(String param) {
+            throw new RuntimeException("NOT IMPLEMENTED");
+        }
 
         @Override
         public String getMethod() {
@@ -90,7 +95,7 @@ public class JaxrsHttpFacade implements HttpFacade {
         }
     }
 
-    protected class ResponseFacade implements HttpFacade.Response {
+    protected class ResponseFacade implements OIDCHttpFacade.Response {
 
         private javax.ws.rs.core.Response.ResponseBuilder responseBuilder = javax.ws.rs.core.Response.status(204);
 
diff --git a/integration/jboss-adapter-core/pom.xml b/integration/jboss-adapter-core/pom.xml
index 161a8af..559fd8b 100755
--- a/integration/jboss-adapter-core/pom.xml
+++ b/integration/jboss-adapter-core/pom.xml
@@ -26,6 +26,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml
index 81625f4..8b3ac6c 100755
--- a/integration/jetty/jetty8.1/pom.xml
+++ b/integration/jetty/jetty8.1/pom.xml
@@ -40,6 +40,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-jetty-core</artifactId>
         </dependency>
 		<dependency>
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/pom.xml b/integration/jetty/jetty-adapter-spi/pom.xml
new file mode 100755
index 0000000..714de2b
--- /dev/null
+++ b/integration/jetty/jetty-adapter-spi/pom.xml
@@ -0,0 +1,115 @@
+<?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.6.0.Final-SNAPSHOT</version>
+		<relativePath>../../../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-jetty-adapter-spi</artifactId>
+	<name>Keycloak Jetty Adapter SPI</name>
+    <properties>
+        <jetty9.version>8.1.16.v20140903</jetty9.version>
+        <keycloak.osgi.export>
+            org.keycloak.adapters.jetty.core.*
+        </keycloak.osgi.export>
+        <keycloak.osgi.import>
+            org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
+            javax.servlet.*;version="[2.5,4)";resolution:=optional,
+            org.keycloak.*;version="${project.version}",
+            *;resolution:=optional
+        </keycloak.osgi.import>
+    </properties>
+	<description />
+
+	<dependencies>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+        </dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${jetty9.version}</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${jetty9.version}</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-security</artifactId>
+            <version>${jetty9.version}</version>
+            <scope>compile</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>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+
+            <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <id>bundle-manifest</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <instructions>
+                        <Bundle-ClassPath>.</Bundle-ClassPath>
+                        <Bundle-Name>${project.name}</Bundle-Name>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>${keycloak.osgi.import}</Import-Package>
+                        <Export-Package>${keycloak.osgi.export}</Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/integration/jetty/jetty-core/pom.xml b/integration/jetty/jetty-core/pom.xml
index 93504df..4628c6b 100755
--- a/integration/jetty/jetty-core/pom.xml
+++ b/integration/jetty/jetty-core/pom.xml
@@ -35,6 +35,14 @@
 			<groupId>org.keycloak</groupId>
 			<artifactId>keycloak-core</artifactId>
 		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-jetty-adapter-spi</artifactId>
+        </dependency>
 		<dependency>
 			<groupId>org.keycloak</groupId>
 			<artifactId>keycloak-adapter-core</artifactId>
diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
index bde5ad0..c11f30a 100755
--- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
@@ -92,7 +92,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
         AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
         KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
-            JettyHttpFacade facade = new JettyHttpFacade(request, null);
+            JettyHttpFacade facade = new OIDCJettyHttpFacade(request, null);
             KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
             if (ksc instanceof RefreshableKeycloakSecurityContext) {
                 ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
@@ -229,7 +229,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
             log.trace("*** authenticate");
         }
         Request request = resolveRequest(req);
-        JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse) res);
+        OIDCJettyHttpFacade facade = new OIDCJettyHttpFacade(request, (HttpServletResponse) res);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment == null || !deployment.isConfigured()) {
             log.debug("*** deployment isn't configured return false");
diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/OIDCJettyHttpFacade.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/OIDCJettyHttpFacade.java
new file mode 100755
index 0000000..3f6bd0b
--- /dev/null
+++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/OIDCJettyHttpFacade.java
@@ -0,0 +1,23 @@
+package org.keycloak.adapters.jetty.core;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCJettyHttpFacade extends JettyHttpFacade implements OIDCHttpFacade {
+
+    public OIDCJettyHttpFacade(org.eclipse.jetty.server.Request request, HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+    }
+
+}
diff --git a/integration/jetty/pom.xml b/integration/jetty/pom.xml
index 8388996..3219087 100755
--- a/integration/jetty/pom.xml
+++ b/integration/jetty/pom.xml
@@ -14,6 +14,7 @@
     <packaging>pom</packaging>
 
     <modules>
+        <module>jetty-adapter-spi</module>
         <module>jetty-core</module>
         <module>jetty8.1</module>
         <module>jetty9.2</module>
diff --git a/integration/pom.xml b/integration/pom.xml
index 3327b6d..e4b7f27 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -14,6 +14,7 @@
     <packaging>pom</packaging>
 
     <modules>
+        <module>adapter-spi</module>
         <module>adapter-core</module>
         <module>jaxrs-oauth-client</module>
         <module>servlet-oauth-client</module>
@@ -21,6 +22,7 @@
         <module>tomcat</module>
         <module>as7-eap6</module>
         <module>jetty</module>
+        <module>undertow-adapter-spi</module>
         <module>undertow</module>
         <module>wildfly</module>
         <module>js</module>
diff --git a/integration/servlet-oauth-client/pom.xml b/integration/servlet-oauth-client/pom.xml
index b5f5d47..721c3bd 100755
--- a/integration/servlet-oauth-client/pom.xml
+++ b/integration/servlet-oauth-client/pom.xml
@@ -26,6 +26,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
             <scope>provided</scope>
         </dependency>
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 72501ac..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
@@ -3,15 +3,13 @@ package org.keycloak.servlet;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.adapters.AdapterDeploymentContext;
-import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OIDCHttpFacade;
 import org.keycloak.adapters.ServerRequest;
-import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
 import org.keycloak.util.KeycloakUriBuilder;
-import org.keycloak.util.UriUtils;
 
 import javax.security.cert.X509Certificate;
 import javax.servlet.http.Cookie;
@@ -167,7 +165,7 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
     }
 
 
-    public static class ServletFacade implements HttpFacade {
+    public static class ServletFacade implements OIDCHttpFacade {
 
         private final HttpServletRequest servletRequest;
 
@@ -185,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-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java
old mode 100644
new mode 100755
index c5834c2..6a37add
--- a/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java
+++ b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java
@@ -1,8 +1,8 @@
 package org.keycloak.adapters.springboot;
 
-import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 
 public class KeycloakSpringBootConfigResolver implements org.keycloak.adapters.KeycloakConfigResolver {
@@ -12,7 +12,7 @@ public class KeycloakSpringBootConfigResolver implements org.keycloak.adapters.K
     private static AdapterConfig adapterConfig;
 
     @Override
-    public KeycloakDeployment resolve(HttpFacade.Request request) {
+    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
         if (keycloakDeployment != null) {
             return keycloakDeployment;
         }
diff --git a/integration/spring-security/pom.xml b/integration/spring-security/pom.xml
index 718f342..17783fd 100755
--- a/integration/spring-security/pom.xml
+++ b/integration/spring-security/pom.xml
@@ -28,6 +28,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java
old mode 100644
new mode 100755
index 586dcba..50d0668
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java
@@ -1,4 +1,4 @@
 /**
- * Provides an {@link org.keycloak.adapters.HttpFacade} implementation.
+ * Provides an {@link org.keycloak.adapters.OIDCHttpFacade} implementation.
  */
 package org.keycloak.adapters.springsecurity.facade;
\ No newline at end of file
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java
old mode 100644
new mode 100755
index 8c7a5b0..b8d7800
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java
@@ -1,9 +1,7 @@
 package org.keycloak.adapters.springsecurity.facade;
 
 import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.adapters.HttpFacade;
-import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletRequest;
-import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletResponse;
+import org.keycloak.adapters.OIDCHttpFacade;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.util.Assert;
@@ -13,12 +11,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * Simple {@link HttpFacade} wrapping an {@link HttpServletRequest} and {@link HttpServletResponse}.
+ * Simple {@link org.keycloak.adapters.OIDCHttpFacade} wrapping an {@link HttpServletRequest} and {@link HttpServletResponse}.
  *
  * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
  * @version $Revision: 1 $
  */
-public class SimpleHttpFacade implements HttpFacade {
+public class SimpleHttpFacade implements OIDCHttpFacade {
 
     private final HttpServletRequest request;
     private final HttpServletResponse response;
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/pom.xml b/integration/tomcat/pom.xml
index c5808bf..23f457e 100755
--- a/integration/tomcat/pom.xml
+++ b/integration/tomcat/pom.xml
@@ -14,6 +14,7 @@
     <packaging>pom</packaging>
 
     <modules>
+        <module>tomcat-adapter-spi</module>
         <module>tomcat-core</module>
         <module>tomcat6</module>
         <module>tomcat7</module>
diff --git a/integration/tomcat/tomcat-adapter-spi/pom.xml b/integration/tomcat/tomcat-adapter-spi/pom.xml
new file mode 100755
index 0000000..eb70711
--- /dev/null
+++ b/integration/tomcat/tomcat-adapter-spi/pom.xml
@@ -0,0 +1,69 @@
+<?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.6.0.Final-SNAPSHOT</version>
+		<relativePath>../../../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-tomcat-adapter-spi</artifactId>
+	<name>Keycloak Tomcat Adapter SPI</name>
+    <properties>
+        <!-- <tomcat.version>8.0.14</tomcat.version> -->
+        <!-- <tomcat.version>7.0.52</tomcat.version> -->
+        <tomcat.version>6.0.41</tomcat.version>
+    </properties>
+	<description />
+
+	<dependencies>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <!--
+		<dependency>
+			<groupId>org.apache.tomcat</groupId>
+			<artifactId>tomcat-servlet-api</artifactId>
+			<version>${tomcat.version}</version>
+			<scope>compile</scope>
+		</dependency>
+		-->
+		<dependency>
+			<groupId>org.apache.tomcat</groupId>
+			<artifactId>catalina</artifactId>
+            <version>${tomcat.version}</version>
+			<scope>compile</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>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/integration/tomcat/tomcat-core/pom.xml b/integration/tomcat/tomcat-core/pom.xml
index 4271b7f..25c7b6b 100755
--- a/integration/tomcat/tomcat-core/pom.xml
+++ b/integration/tomcat/tomcat-core/pom.xml
@@ -28,6 +28,14 @@
 			<groupId>org.keycloak</groupId>
 			<artifactId>keycloak-core</artifactId>
 		</dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+        </dependency>
 		<dependency>
 			<groupId>org.keycloak</groupId>
 			<artifactId>keycloak-adapter-core</artifactId>
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
index 80663c8..3347978 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
@@ -5,13 +5,9 @@ import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Manager;
-import org.apache.catalina.authenticator.Constants;
 import org.apache.catalina.authenticator.FormAuthenticator;
-import org.apache.catalina.authenticator.SavedRequest;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
-import org.apache.catalina.deploy.LoginConfig;
-import org.apache.tomcat.util.buf.ByteChunk;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.adapters.AdapterDeploymentContext;
@@ -28,15 +24,12 @@ import org.keycloak.enums.TokenStore;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.Locale;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.keycloak.adapters.KeycloakConfigResolver;
@@ -71,7 +64,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
     protected void logoutInternal(Request request) {
         KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
         if (ksc != null) {
-            CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
+            CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, null);
             KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
             if (ksc instanceof RefreshableKeycloakSecurityContext) {
                 ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
@@ -164,7 +157,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
     @Override
     public void invoke(Request request, Response response) throws IOException, ServletException {
         try {
-            CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
+            CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
             Manager sessionManager = request.getContext().getManager();
             CatalinaUserSessionManagementWrapper sessionManagementWrapper = new CatalinaUserSessionManagementWrapper(userSessionManagement, sessionManager);
             PreAuthActionsHandler handler = new PreAuthActionsHandler(sessionManagementWrapper, deploymentContext, facade);
@@ -181,7 +174,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
     protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
 
     protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
-        CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
+        CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment == null || !deployment.isConfigured()) {
             return false;
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java
index 8728f36..6b42b85 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java
@@ -39,10 +39,10 @@ public class AuthenticatedActionsValve extends ValveBase {
     @Override
     public void invoke(Request request, Response response) throws IOException, ServletException {
         log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
-        CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
+        CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment != null && deployment.isConfigured()) {
-            AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
+            AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new OIDCCatalinaHttpFacade(request, response));
             if (handler.handledRequest()) {
                 return;
             }
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/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/OIDCCatalinaHttpFacade.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/OIDCCatalinaHttpFacade.java
new file mode 100755
index 0000000..b77337e
--- /dev/null
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/OIDCCatalinaHttpFacade.java
@@ -0,0 +1,23 @@
+package org.keycloak.adapters.tomcat;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCCatalinaHttpFacade extends CatalinaHttpFacade implements OIDCHttpFacade{
+
+    public OIDCCatalinaHttpFacade(org.apache.catalina.connector.Request request, HttpServletResponse response) {
+        super(response, request);
+    }
+
+    @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+    }
+
+}
diff --git a/integration/undertow/pom.xml b/integration/undertow/pom.xml
index 7a1d7b9..4dc9dfb 100755
--- a/integration/undertow/pom.xml
+++ b/integration/undertow/pom.xml
@@ -26,6 +26,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
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 892343b..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
@@ -21,18 +21,14 @@ 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.server.session.Session;
 import io.undertow.util.AttachmentKey;
 import io.undertow.util.Headers;
-import io.undertow.util.Sessions;
 import io.undertow.util.StatusCodes;
-import org.keycloak.KeycloakPrincipal;
 import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
-import org.keycloak.adapters.CookieTokenStore;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
@@ -93,9 +89,9 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
                 if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
 
                 HttpServerExchange exchange = notification.getExchange();
-                UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+                UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
                 KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
-                KeycloakSecurityContext ksc = exchange.getAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
+                KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
                 if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
                     ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
                 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
index f0e467b..84c5fdd 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
@@ -48,7 +48,7 @@ public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthen
     }
 
     protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
-        exchange.putAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
+        exchange.putAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
     }
 
     @Override
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java
new file mode 100755
index 0000000..9063399
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java
@@ -0,0 +1,40 @@
+/*
+ * 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.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCUndertowHttpFacade extends UndertowHttpFacade implements OIDCHttpFacade {
+    public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
+    public OIDCUndertowHttpFacade(HttpServerExchange exchange) {
+        super(exchange);
+    }
+
+    @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+    }
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index f4b6ef3..bfa2cb3 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -16,7 +16,6 @@
  */
 package org.keycloak.adapters.undertow;
 
-import io.undertow.security.api.AuthenticationMechanism;
 import io.undertow.security.api.SecurityContext;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.api.ConfidentialPortManager;
@@ -25,7 +24,6 @@ import io.undertow.util.Headers;
 import org.jboss.logging.Logger;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
-import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.NodesRegistrationManagement;
@@ -81,7 +79,7 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
 
     @Override
     public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
-        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (!deployment.isConfigured()) {
             return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
index 61e66c4..362cdb0 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
@@ -61,7 +61,7 @@ public class ServletPreAuthActionsHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
         PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
index a21272e..4e7590f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
@@ -57,7 +57,7 @@ public class UndertowAuthenticatedActionsHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        OIDCUndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment != null && deployment.isConfigured()) {
             AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, facade);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
index b55067b..85b7581 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
@@ -25,7 +25,7 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut
 
     @Override
     public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
-        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (!deployment.isConfigured()) {
             return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
index a68f39f..77194e1 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
@@ -47,7 +47,7 @@ public class UndertowPreAuthActionsHandler implements HttpHandler {
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
-        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
         SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
         PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
         if (handler.handleRequest()) return;
diff --git a/integration/undertow-adapter-spi/pom.xml b/integration/undertow-adapter-spi/pom.xml
new file mode 100755
index 0000000..91a649d
--- /dev/null
+++ b/integration/undertow-adapter-spi/pom.xml
@@ -0,0 +1,65 @@
+<?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.6.0.Final-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-undertow-adapter-spi</artifactId>
+    <name>Keycloak Undertow Integration</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-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </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/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/wildfly/wildfly-adapter/pom.xml b/integration/wildfly/wildfly-adapter/pom.xml
index 3685b1b..e3803dc 100755
--- a/integration/wildfly/wildfly-adapter/pom.xml
+++ b/integration/wildfly/wildfly-adapter/pom.xml
@@ -26,10 +26,18 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-adapter-core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-undertow-adapter</artifactId>
         </dependency>
         <dependency>

pom.xml 30(+30 -0)

diff --git a/pom.xml b/pom.xml
index bf0e5d6..1b1d323 100755
--- a/pom.xml
+++ b/pom.xml
@@ -756,6 +756,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-adapter-spi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-adapter-core</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -786,6 +791,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-jetty-adapter-spi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-jetty-core</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -856,6 +866,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-tomcat-core-adapter</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -876,6 +891,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-undertow-adapter-spi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-undertow-adapter</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -956,6 +976,16 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-saml-adapter-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-undertow-saml-adapter</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/pom.xml b/saml/client-adapter/core/pom.xml
new file mode 100755
index 0000000..605d87f
--- /dev/null
+++ b/saml/client-adapter/core/pom.xml
@@ -0,0 +1,60 @@
+<?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.6.0.Final-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-saml-adapter-core</artifactId>
+    <name>Keycloak SAML Client Adapter Core</name>
+    <description/>
+
+    <properties>
+        <timestamp>${maven.build.timestamp}</timestamp>
+        <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
+    </properties>
+    <dependencies>
+        <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.jboss.logging</groupId>
+            <artifactId>jboss-logging</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/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
new file mode 100755
index 0000000..84764e3
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
@@ -0,0 +1,172 @@
+package org.keycloak.adapters.saml.config;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class IDP implements Serializable {
+    public static class SingleSignOnService implements Serializable {
+        private boolean signRequest;
+        private boolean validateResponseSignature;
+        private String requestBinding;
+        private String responseBinding;
+        private String bindingUrl;
+
+        public boolean isSignRequest() {
+            return signRequest;
+        }
+
+        public void setSignRequest(boolean signRequest) {
+            this.signRequest = signRequest;
+        }
+
+        public boolean isValidateResponseSignature() {
+            return validateResponseSignature;
+        }
+
+        public void setValidateResponseSignature(boolean validateResponseSignature) {
+            this.validateResponseSignature = validateResponseSignature;
+        }
+
+        public String getRequestBinding() {
+            return requestBinding;
+        }
+
+        public void setRequestBinding(String requestBinding) {
+            this.requestBinding = requestBinding;
+        }
+
+        public String getResponseBinding() {
+            return responseBinding;
+        }
+
+        public void setResponseBinding(String responseBinding) {
+            this.responseBinding = responseBinding;
+        }
+
+        public String getBindingUrl() {
+            return bindingUrl;
+        }
+
+        public void setBindingUrl(String bindingUrl) {
+            this.bindingUrl = bindingUrl;
+        }
+    }
+
+    public static class SingleLogoutService implements Serializable {
+        private boolean signRequest;
+        private boolean signResponse;
+        private boolean validateRequestSignature;
+        private boolean validateResponseSignature;
+        private String requestBinding;
+        private String responseBinding;
+        private String postBindingUrl;
+        private String redirectBindingUrl;
+
+        public boolean isSignRequest() {
+            return signRequest;
+        }
+
+        public void setSignRequest(boolean signRequest) {
+            this.signRequest = signRequest;
+        }
+
+        public boolean isSignResponse() {
+            return signResponse;
+        }
+
+        public void setSignResponse(boolean signResponse) {
+            this.signResponse = signResponse;
+        }
+
+        public boolean isValidateRequestSignature() {
+            return validateRequestSignature;
+        }
+
+        public void setValidateRequestSignature(boolean validateRequestSignature) {
+            this.validateRequestSignature = validateRequestSignature;
+        }
+
+        public boolean isValidateResponseSignature() {
+            return validateResponseSignature;
+        }
+
+        public void setValidateResponseSignature(boolean validateResponseSignature) {
+            this.validateResponseSignature = validateResponseSignature;
+        }
+
+        public String getRequestBinding() {
+            return requestBinding;
+        }
+
+        public void setRequestBinding(String requestBinding) {
+            this.requestBinding = requestBinding;
+        }
+
+        public String getResponseBinding() {
+            return responseBinding;
+        }
+
+        public void setResponseBinding(String responseBinding) {
+            this.responseBinding = responseBinding;
+        }
+
+        public String getPostBindingUrl() {
+            return postBindingUrl;
+        }
+
+        public void setPostBindingUrl(String postBindingUrl) {
+            this.postBindingUrl = postBindingUrl;
+        }
+
+        public String getRedirectBindingUrl() {
+            return redirectBindingUrl;
+        }
+
+        public void setRedirectBindingUrl(String redirectBindingUrl) {
+            this.redirectBindingUrl = redirectBindingUrl;
+        }
+    }
+
+    private String entityID;
+    private SingleSignOnService singleSignOnService;
+    private SingleLogoutService singleLogoutService;
+    private List<Key> keys;
+
+    public String getEntityID() {
+        return entityID;
+    }
+
+    public void setEntityID(String entityID) {
+        this.entityID = entityID;
+    }
+
+    public SingleSignOnService getSingleSignOnService() {
+        return singleSignOnService;
+    }
+
+    public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
+        this.singleSignOnService = singleSignOnService;
+    }
+
+    public SingleLogoutService getSingleLogoutService() {
+        return singleLogoutService;
+    }
+
+    public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
+        this.singleLogoutService = singleLogoutService;
+    }
+
+    public List<Key> getKeys() {
+        return keys;
+    }
+
+    public void setKeys(List<Key> keys) {
+        this.keys = keys;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/Key.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/Key.java
new file mode 100755
index 0000000..3d94f78
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/Key.java
@@ -0,0 +1,143 @@
+package org.keycloak.adapters.saml.config;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Key implements Serializable {
+
+    public static class KeyStoreConfig implements Serializable {
+        private String file;
+        private String resource;
+        private String password;
+        private String type;
+        private String alias;
+        private String privateKeyAlias;
+        private String privateKeyPassword;
+        private String certificateAlias;
+
+
+        public String getFile() {
+            return file;
+        }
+
+        public void setFile(String file) {
+            this.file = file;
+        }
+
+        public String getResource() {
+            return resource;
+        }
+
+        public void setResource(String resource) {
+            this.resource = resource;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public void setPassword(String password) {
+            this.password = password;
+        }
+
+        public String getPrivateKeyAlias() {
+            return privateKeyAlias;
+        }
+
+        public void setPrivateKeyAlias(String privateKeyAlias) {
+            this.privateKeyAlias = privateKeyAlias;
+        }
+
+        public String getPrivateKeyPassword() {
+            return privateKeyPassword;
+        }
+
+        public void setPrivateKeyPassword(String privateKeyPassword) {
+            this.privateKeyPassword = privateKeyPassword;
+        }
+
+        public String getCertificateAlias() {
+            return certificateAlias;
+        }
+
+        public void setCertificateAlias(String certificateAlias) {
+            this.certificateAlias = certificateAlias;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getAlias() {
+            return alias;
+        }
+
+        public void setAlias(String alias) {
+            this.alias = alias;
+        }
+    }
+
+
+    private boolean signing;
+    private boolean encryption;
+    private KeyStoreConfig keystore;
+    private String privateKeyPem;
+    private String publicKeyPem;
+    private String certificatePem;
+
+
+    public boolean isSigning() {
+        return signing;
+    }
+
+    public void setSigning(boolean signing) {
+        this.signing = signing;
+    }
+
+    public boolean isEncryption() {
+        return encryption;
+    }
+
+    public void setEncryption(boolean encryption) {
+        this.encryption = encryption;
+    }
+
+    public KeyStoreConfig getKeystore() {
+        return keystore;
+    }
+
+    public void setKeystore(KeyStoreConfig keystore) {
+        this.keystore = keystore;
+    }
+
+    public String getPrivateKeyPem() {
+        return privateKeyPem;
+    }
+
+    public void setPrivateKeyPem(String privateKeyPem) {
+        this.privateKeyPem = privateKeyPem;
+    }
+
+    public String getPublicKeyPem() {
+        return publicKeyPem;
+    }
+
+    public void setPublicKeyPem(String publicKeyPem) {
+        this.publicKeyPem = publicKeyPem;
+    }
+
+    public String getCertificatePem() {
+        return certificatePem;
+    }
+
+    public void setCertificatePem(String certificatePem) {
+        this.certificatePem = certificatePem;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java
new file mode 100755
index 0000000..9370dba
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java
@@ -0,0 +1,21 @@
+package org.keycloak.adapters.saml.config;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapter implements Serializable {
+    private List<SP> sps = new LinkedList<>();
+
+    public List<SP> getSps() {
+        return sps;
+    }
+
+    public void setSps(List<SP> sps) {
+        this.sps = sps;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
new file mode 100755
index 0000000..0e0542c
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -0,0 +1,57 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ConfigXmlConstants {
+    public static final String KEYCLOAK_SAML_ADAPTER ="keycloak-saml-adapter";
+    public static final String SP_ELEMENT="SP";
+    public static final String ENTITY_ID_ATTR = "entityID";
+    public static final String SSL_POLICY_ATTR = "sslPolicy";
+    public static final String NAME_ID_POLICY_FORMAT_ATTR = "nameIDPolicyFormat";
+    public static final String FORCE_AUTHENTICATION_ATTR = "forceAuthentication";
+    public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
+    public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
+    public static final String LOGOUT_PAGE_ATTR = "logoutPage";
+
+
+    public static final String KEYS_ELEMENT = "Keys";
+    public static final String KEY_ELEMENT = "Key";
+    public static final String SIGNING_ATTR = "signing";
+    public static final String ENCRYPTION_ATTR = "encryption";
+    public static final String CERTIFICATE_PEM_ELEMENT = "CertificatePem";
+    public static final String PRIVATE_KEY_PEM_ELEMENT = "PrivateKeyPem";
+    public static final String PUBLIC_KEY_PEM_ELEMENT = "PublicKeyPem";
+    public static final String FILE_ATTR = "file";
+    public static final String TYPE_ATTR = "type";
+    public static final String RESOURCE_ATTR = "resource";
+    public static final String PASSWORD_ATTR = "password";
+    public static final String ALIAS_ATTR = "alias";
+    public static final String KEYS_STORE_ELEMENT = "KeyStore";
+    public static final String CERTIFICATE_ELEMENT = "Certificate";
+    public static final String PRIVATE_KEY_ELEMENT = "PrivateKey";
+
+    public static final String PRINCIPAL_NAME_MAPPING_ELEMENT = "PrincipalNameMapping";
+    public static final String POLICY_ATTR = "policy";
+    public static final String ATTRIBUTE_ATTR = "attribute";
+
+
+    public static final String ROLE_MAPPING_ELEMENT = "RoleMapping";
+    public static final String ATTRIBUTE_ELEMENT = "Attribute";
+    public static final String FRIENDLY_ATTRIBUTE_ELEMENT = "FriendlyAttribute";
+    public static final String NAME_ATTR = "name";
+
+    public static final String IDP_ELEMENT = "IDP";
+    public static final String SINGLE_SIGN_ON_SERVICE_ELEMENT = "SingleSignOnService";
+    public static final String SINGLE_LOGOUT_SERVICE_ELEMENT = "SingleLogoutService";
+    public static final String SIGN_REQUEST_ATTR = "signRequest";
+    public static final String SIGN_RESPONSE_ATTR = "signResponse";
+    public static final String REQUEST_BINDING_ATTR = "requestBinding";
+    public static final String RESPONSE_BINDING_ATTR = "responseBinding";
+    public static final String BINDING_URL_ATTR = "bindingUrl";
+    public static final String VALIDATE_RESPONSE_SIGNATURE_ATTR = "validateResponseSignature";
+    public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
+    public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
+    public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
new file mode 100755
index 0000000..b72cb41
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -0,0 +1,206 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.util.PemUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DeploymentBuilder {
+    public SamlDeployment build(InputStream xml, ResourceLoader resourceLoader) throws ParsingException {
+        DefaultSamlDeployment deployment = new DefaultSamlDeployment();
+        DefaultSamlDeployment.DefaultIDP idp = new DefaultSamlDeployment.DefaultIDP();
+        DefaultSamlDeployment.DefaultSingleSignOnService sso = new DefaultSamlDeployment.DefaultSingleSignOnService();
+        DefaultSamlDeployment.DefaultSingleLogoutService slo = new DefaultSamlDeployment.DefaultSingleLogoutService();
+        idp.setSingleSignOnService(sso);
+        idp.setSingleLogoutService(slo);
+
+        KeycloakSamlAdapter adapter = (KeycloakSamlAdapter)(new KeycloakSamlAdapterXMLParser().parse(xml));
+        SP sp = adapter.getSps().get(0);
+        deployment.setConfigured(true);
+        deployment.setEntityID(sp.getEntityID());
+        deployment.setForceAuthentication(sp.isForceAuthentication());
+        deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
+        deployment.setLogoutPage(sp.getLogoutPage());
+        deployment.setSignatureCanonicalizationMethod(sp.getSignatureCanonicalizationMethod());
+        deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
+        if (sp.getSignatureAlgorithm() != null) {
+            deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getSignatureAlgorithm()));
+        }
+        if (sp.getPrincipalNameMapping() != null) {
+            SamlDeployment.PrincipalNamePolicy policy = SamlDeployment.PrincipalNamePolicy.valueOf(sp.getPrincipalNameMapping().getPolicy());
+            deployment.setPrincipalNamePolicy(policy);
+            deployment.setPrincipalAttributeName(sp.getPrincipalNameMapping().getAttributeName());
+        }
+        deployment.setRoleAttributeNames(sp.getRoleAttributes());
+        deployment.setRoleFriendlyAttributeNames(sp.getRoleFriendlyAttributes());
+        if (sp.getSslPolicy() != null) {
+            SslRequired ssl = SslRequired.valueOf(sp.getSslPolicy());
+            deployment.setSslRequired(ssl);
+        }
+        if (sp.getKeys() != null) {
+            for (Key key : sp.getKeys()) {
+                if (key.isSigning()) {
+                    PrivateKey privateKey = null;
+                    PublicKey publicKey = null;
+                    if (key.getKeystore() != null) {
+                        KeyStore keyStore = loadKeystore(resourceLoader, key);
+                        Certificate cert = null;
+                        try {
+                            cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+                            privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                        publicKey = cert.getPublicKey();
+                    } else {
+                        if (key.getPrivateKeyPem() == null) {
+                            throw new RuntimeException("SP signing key must have a PrivateKey defined");
+                        }
+                        try {
+                            privateKey = PemUtils.decodePrivateKey(key.getPrivateKeyPem().trim());
+                            if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+                                throw new RuntimeException("Sp signing key must have a PublicKey or Certificate defined");
+                            }
+                            publicKey = getPublicKeyFromPem(key);
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                    KeyPair keyPair = new KeyPair(publicKey, privateKey);
+                    deployment.setSigningKeyPair(keyPair);
+
+                }
+                if (key.isEncryption()) {
+                    KeyStore keyStore = loadKeystore(resourceLoader, key);
+                    try {
+                        PrivateKey privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
+                        deployment.setDecryptionKey(privateKey);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+
+        deployment.setIdp(idp);
+        idp.setEntityID(sp.getIdp().getEntityID());
+        sso.setRequestBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getRequestBinding()));
+        sso.setRequestBindingUrl(sp.getIdp().getSingleSignOnService().getBindingUrl());
+        if (sp.getIdp().getSingleSignOnService().getResponseBinding() != null) {
+            sso.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getResponseBinding()));
+        }
+        sso.setSignRequest(sp.getIdp().getSingleSignOnService().isSignRequest());
+        sso.setValidateResponseSignature(sp.getIdp().getSingleSignOnService().isValidateResponseSignature());
+
+        slo.setSignRequest(sp.getIdp().getSingleLogoutService().isSignRequest());
+        slo.setSignResponse(sp.getIdp().getSingleLogoutService().isSignResponse());
+        slo.setValidateResponseSignature(sp.getIdp().getSingleLogoutService().isValidateResponseSignature());
+        slo.setValidateRequestSignature(sp.getIdp().getSingleLogoutService().isValidateRequestSignature());
+        slo.setRequestBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleLogoutService().getRequestBinding()));
+        slo.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleLogoutService().getResponseBinding()));
+        if (slo.getRequestBinding() == SamlDeployment.Binding.POST) {
+            slo.setRequestBindingUrl(sp.getIdp().getSingleLogoutService().getPostBindingUrl());
+        } else {
+            slo.setRequestBindingUrl(sp.getIdp().getSingleLogoutService().getRedirectBindingUrl());
+        }
+        if (slo.getResponseBinding() == SamlDeployment.Binding.POST) {
+            slo.setResponseBindingUrl(sp.getIdp().getSingleLogoutService().getPostBindingUrl());
+        } else {
+            slo.setResponseBindingUrl(sp.getIdp().getSingleLogoutService().getRedirectBindingUrl());
+        }
+        if (sp.getIdp().getKeys() != null) {
+            for (Key key : sp.getIdp().getKeys()) {
+                if (key.isSigning()) {
+                    if (key.getKeystore() != null) {
+                        KeyStore keyStore = loadKeystore(resourceLoader, key);
+                        Certificate cert = null;
+                        try {
+                            cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+                        } catch (KeyStoreException e) {
+                            throw new RuntimeException(e);
+                        }
+                        idp.setSignatureValidationKey(cert.getPublicKey());
+                    } else {
+                        if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+                            throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
+                        }
+                        try {
+                            PublicKey publicKey = getPublicKeyFromPem(key);
+                            idp.setSignatureValidationKey(publicKey);
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        return deployment;
+    }
+
+    protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
+        PublicKey publicKey;
+        if (key.getPublicKeyPem() != null) {
+            publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
+        } else {
+            Certificate cert = PemUtils.decodeCertificate(key.getCertificatePem().trim());
+            publicKey = cert.getPublicKey();
+        }
+        return publicKey;
+    }
+
+    protected static KeyStore loadKeystore(ResourceLoader resourceLoader, Key key) {
+        String type = key.getKeystore().getType();
+        if (type == null) type = "JKS";
+        KeyStore keyStore = null;
+        try {
+            keyStore = KeyStore.getInstance(type);
+        } catch (KeyStoreException e) {
+            throw new RuntimeException(e);
+        }
+        InputStream is = null;
+        if (key.getKeystore().getFile() != null) {
+            File fp = new File(key.getKeystore().getFile());
+            if (!fp.exists()) {
+            }
+            try {
+                is = new FileInputStream(fp);
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException("KeyStore " + key.getKeystore().getFile() + " does not exist");
+            }
+
+        } else {
+            is = resourceLoader.getResourceAsStream(key.getKeystore().getResource());
+            if (is == null) {
+                throw new RuntimeException("KeyStore " + key.getKeystore().getResource() + " does not exist");
+            }
+        }
+        try {
+            keyStore.load(is, key.getKeystore().getPassword().toCharArray());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return keyStore;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
new file mode 100755
index 0000000..96485d5
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
@@ -0,0 +1,98 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class IDPXmlParser extends AbstractParser {
+
+    @Override
+    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
+        IDP idp = new IDP();
+        String entityID = StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+        if (entityID == null) {
+            throw new ParsingException("entityID must be set on IDP");
+
+        }
+        idp.setEntityID(entityID);
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.IDP_ELEMENT))
+                    break;
+                else
+                    continue;
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.SINGLE_SIGN_ON_SERVICE_ELEMENT)) {
+                IDP.SingleSignOnService sso = parseSingleSignOnService(xmlEventReader);
+                idp.setSingleSignOnService(sso);
+
+            } else if (tag.equals(ConfigXmlConstants.SINGLE_LOGOUT_SERVICE_ELEMENT)) {
+                IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader);
+                idp.setSingleLogoutService(slo);
+
+            } else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
+                KeysXmlParser parser = new KeysXmlParser();
+                List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
+                idp.setKeys(keys);
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return idp;
+    }
+
+    protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader) throws ParsingException {
+        IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
+        StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+        slo.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR));
+        slo.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR));
+        slo.setValidateRequestSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR));
+        slo.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+        slo.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+        slo.setSignResponse(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR));
+        slo.setPostBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
+        slo.setRedirectBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
+        return slo;
+    }
+
+    protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader) throws ParsingException {
+        IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
+        StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+        sso.setSignRequest(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR));
+        sso.setValidateResponseSignature(StaxParserUtil.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR));
+        sso.setRequestBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+        sso.setResponseBinding(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+        sso.setBindingUrl(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
+        return sso;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        return false;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java
new file mode 100755
index 0000000..d5b9ee6
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java
@@ -0,0 +1,46 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapterXMLParser extends AbstractParser {
+
+    @Override
+    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+        KeycloakSamlAdapter adapter = new KeycloakSamlAdapter();
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYCLOAK_SAML_ADAPTER);
+        while (xmlEventReader.hasNext()) {
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.SP_ELEMENT)) {
+                SPXmlParser parser = new SPXmlParser();
+                SP sp = (SP)parser.parse(xmlEventReader);
+                if (sp != null) adapter.getSps().add(sp);
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return adapter;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        return false;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java
new file mode 100755
index 0000000..a63a6ac
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java
@@ -0,0 +1,60 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeysXmlParser extends AbstractParser {
+
+    @Override
+    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYS_ELEMENT);
+        List<Key> keys = new LinkedList<>();
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.KEYS_ELEMENT))
+                    break;
+                else
+                    throw logger.parserUnknownEndElement(endElementName);
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.KEY_ELEMENT)) {
+                KeyXmlParser parser = new KeyXmlParser();
+                Key key = (Key)parser.parse(xmlEventReader);
+                keys.add(key);
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return keys;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        return false;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java
new file mode 100755
index 0000000..787af69
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java
@@ -0,0 +1,128 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeyXmlParser extends AbstractParser {
+
+    @Override
+    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.KEY_ELEMENT);
+        Key key = new Key();
+        key.setSigning(StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNING_ATTR));
+        key.setEncryption(StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.ENCRYPTION_ATTR));
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.KEY_ELEMENT))
+                    break;
+                else
+                    throw logger.parserUnknownEndElement(endElementName);
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.KEYS_STORE_ELEMENT)) {
+                key.setKeystore(parseKeyStore(xmlEventReader));
+            } else if (tag.equals(ConfigXmlConstants.CERTIFICATE_PEM_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                key.setCertificatePem(StaxParserUtil.getElementText(xmlEventReader));
+            } else if (tag.equals(ConfigXmlConstants.PUBLIC_KEY_PEM_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                key.setPublicKeyPem(StaxParserUtil.getElementText(xmlEventReader));
+            } else if (tag.equals(ConfigXmlConstants.PRIVATE_KEY_PEM_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                key.setPrivateKeyPem(StaxParserUtil.getElementText(xmlEventReader));
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return key;
+    }
+
+    protected Key.KeyStoreConfig parseKeyStore(XMLEventReader xmlEventReader)  throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYS_STORE_ELEMENT);
+        Key.KeyStoreConfig keyStore = new Key.KeyStoreConfig();
+        keyStore.setType(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.TYPE_ATTR));
+        keyStore.setAlias(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.ALIAS_ATTR));
+        keyStore.setFile(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.FILE_ATTR));
+        keyStore.setResource(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.RESOURCE_ATTR));
+        if (keyStore.getFile() == null && keyStore.getResource() == null) {
+            throw new ParsingException("KeyStore element must have the url or classpath attribute set");
+        }
+        keyStore.setPassword(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.PASSWORD_ATTR));
+        if (keyStore.getPassword() == null) {
+            throw new ParsingException("KeyStore element must have the password attribute set");
+        }
+
+
+
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.KEYS_STORE_ELEMENT))
+                    break;
+                else
+                    continue;
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.CERTIFICATE_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                keyStore.setCertificateAlias(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.ALIAS_ATTR));
+                if (keyStore.getCertificateAlias() == null) {
+                    throw new ParsingException("KeyStore Certificate element must have the alias attribute set");
+
+                }
+            } else if (tag.equals(ConfigXmlConstants.PRIVATE_KEY_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                keyStore.setPrivateKeyAlias(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.ALIAS_ATTR));
+                if (keyStore.getPrivateKeyAlias() == null) {
+                    throw new ParsingException("KeyStore PrivateKey element must have the alias attribute set");
+
+                }
+                keyStore.setPrivateKeyPassword(StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.PASSWORD_ATTR));
+                if (keyStore.getPrivateKeyPassword() == null) {
+                    throw new ParsingException("KeyStore PrivateKey element must have the password attribute set");
+
+                }
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return keyStore;
+
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        return false;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ResourceLoader.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ResourceLoader.java
new file mode 100755
index 0000000..296f31e
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ResourceLoader.java
@@ -0,0 +1,11 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ResourceLoader {
+    InputStream getResourceAsStream(String resource);
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
new file mode 100755
index 0000000..089dc78
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -0,0 +1,141 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SPXmlParser extends AbstractParser {
+
+    @Override
+    public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.SP_ELEMENT);
+        SP sp = new SP();
+        String entityID = StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+        if (entityID == null) {
+            throw new ParsingException("entityID must be set on SP");
+
+        }
+        sp.setEntityID(entityID);
+        sp.setSslPolicy(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SSL_POLICY_ATTR));
+        sp.setLogoutPage(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.LOGOUT_PAGE_ATTR));
+        sp.setNameIDPolicyFormat(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
+        sp.setSignatureCanonicalizationMethod(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
+        sp.setSignatureAlgorithm(StaxParserUtil.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
+        sp.setForceAuthentication(StaxParserUtil.getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.SP_ELEMENT))
+                    break;
+                else
+                    continue;
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
+                KeysXmlParser parser = new KeysXmlParser();
+                List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
+                sp.setKeys(keys);
+            } else if (tag.equals(ConfigXmlConstants.PRINCIPAL_NAME_MAPPING_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                String policy = StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.POLICY_ATTR);
+                if (policy == null) {
+                    throw new ParsingException("PrincipalNameMapping element must have the policy attribute set");
+
+                }
+                String attribute = StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.ATTRIBUTE_ATTR);
+                SP.PrincipalNameMapping mapping = new SP.PrincipalNameMapping();
+                mapping.setPolicy(policy);
+                mapping.setAttributeName(attribute);
+                sp.setPrincipalNameMapping(mapping);
+
+            } else if (tag.equals(ConfigXmlConstants.ROLE_MAPPING_ELEMENT)) {
+                parseRoleMapping(xmlEventReader, sp);
+            } else if (tag.equals(ConfigXmlConstants.IDP_ELEMENT)) {
+                IDPXmlParser parser = new IDPXmlParser();
+                IDP idp = (IDP)parser.parse(xmlEventReader);
+                sp.setIdp(idp);
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        return sp;
+    }
+
+    protected void parseRoleMapping(XMLEventReader xmlEventReader, SP sp)  throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.ROLE_MAPPING_ELEMENT);
+        Set<String> roleAttributes = new HashSet<>();
+        Set<String> roleFriendlyAttributes = new HashSet<>();
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.ROLE_MAPPING_ELEMENT))
+                    break;
+                else
+                    continue;
+            }
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            if (tag.equals(ConfigXmlConstants.ATTRIBUTE_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                String attributeValue = StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.NAME_ATTR);
+                if (attributeValue == null) {
+                    throw new ParsingException("RoleMapping Attribute element must have the name attribute set");
+
+                }
+                roleAttributes.add(attributeValue);
+            } else if (tag.equals(ConfigXmlConstants.FRIENDLY_ATTRIBUTE_ELEMENT)) {
+                StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+                String attributeValue = StaxParserUtil.getAttributeValue(element, ConfigXmlConstants.NAME_ATTR);
+                if (attributeValue == null) {
+                    throw new ParsingException("RoleMapping FriendlyAttribute element must have the name attribute set");
+
+                }
+                roleFriendlyAttributes.add(attributeValue);
+            } else {
+                StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+            }
+
+        }
+        sp.setRoleAttributes(roleAttributes);
+        sp.setRoleFriendlyAttributes(roleFriendlyAttributes);
+    }
+
+
+    @Override
+    public boolean supports(QName qname) {
+        return false;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
new file mode 100755
index 0000000..bd48ba1
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -0,0 +1,144 @@
+package org.keycloak.adapters.saml.config;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.enums.SslRequired;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SP implements Serializable {
+    public static class PrincipalNameMapping implements Serializable {
+        private String policy;
+        private String attributeName;
+
+        public String getPolicy() {
+            return policy;
+        }
+
+        public void setPolicy(String policy) {
+            this.policy = policy;
+        }
+
+        public String getAttributeName() {
+            return attributeName;
+        }
+
+        public void setAttributeName(String attributeName) {
+            this.attributeName = attributeName;
+        }
+    }
+
+    private String entityID;
+    private String sslPolicy;
+    private boolean forceAuthentication;
+    private String logoutPage;
+    private List<Key> keys;
+    private String nameIDPolicyFormat;
+    private PrincipalNameMapping principalNameMapping;
+    private Set<String> roleAttributes;
+    private Set<String> roleFriendlyAttributes;
+    private String signatureAlgorithm;
+    private String signatureCanonicalizationMethod;
+    private IDP idp;
+
+    public String getEntityID() {
+        return entityID;
+    }
+
+    public void setEntityID(String entityID) {
+        this.entityID = entityID;
+    }
+
+    public String getSslPolicy() {
+        return sslPolicy;
+    }
+
+    public void setSslPolicy(String sslPolicy) {
+        this.sslPolicy = sslPolicy;
+    }
+
+    public boolean isForceAuthentication() {
+        return forceAuthentication;
+    }
+
+    public void setForceAuthentication(boolean forceAuthentication) {
+        this.forceAuthentication = forceAuthentication;
+    }
+
+    public List<Key> getKeys() {
+        return keys;
+    }
+
+    public void setKeys(List<Key> keys) {
+        this.keys = keys;
+    }
+
+    public String getNameIDPolicyFormat() {
+        return nameIDPolicyFormat;
+    }
+
+    public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
+        this.nameIDPolicyFormat = nameIDPolicyFormat;
+    }
+
+    public PrincipalNameMapping getPrincipalNameMapping() {
+        return principalNameMapping;
+    }
+
+    public void setPrincipalNameMapping(PrincipalNameMapping principalNameMapping) {
+        this.principalNameMapping = principalNameMapping;
+    }
+
+    public Set<String> getRoleAttributes() {
+        return roleAttributes;
+    }
+
+    public void setRoleAttributes(Set<String> roleAttributes) {
+        this.roleAttributes = roleAttributes;
+    }
+
+    public Set<String> getRoleFriendlyAttributes() {
+        return roleFriendlyAttributes;
+    }
+
+    public void setRoleFriendlyAttributes(Set<String> roleFriendlyAttributes) {
+        this.roleFriendlyAttributes = roleFriendlyAttributes;
+    }
+
+    public IDP getIdp() {
+        return idp;
+    }
+
+    public void setIdp(IDP idp) {
+        this.idp = idp;
+    }
+
+    public String getLogoutPage() {
+        return logoutPage;
+    }
+
+    public void setLogoutPage(String logoutPage) {
+        this.logoutPage = logoutPage;
+    }
+
+    public String getSignatureAlgorithm() {
+        return signatureAlgorithm;
+    }
+
+    public void setSignatureAlgorithm(String signatureAlgorithm) {
+        this.signatureAlgorithm = signatureAlgorithm;
+    }
+
+    public String getSignatureCanonicalizationMethod() {
+        return signatureCanonicalizationMethod;
+    }
+
+    public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
+        this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
+    }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
new file mode 100755
index 0000000..28a4ead
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -0,0 +1,364 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+
+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 class DefaultSamlDeployment implements SamlDeployment {
+
+    public static class DefaultSingleSignOnService implements IDP.SingleSignOnService {
+        private boolean signRequest;
+        private boolean validateResponseSignature;
+        private Binding requestBinding;
+        private Binding responseBinding;
+        private String requestBindingUrl;
+
+        @Override
+        public boolean signRequest() {
+            return signRequest;
+        }
+
+        @Override
+        public boolean validateResponseSignature() {
+            return validateResponseSignature;
+        }
+
+         @Override
+        public Binding getRequestBinding() {
+            return requestBinding;
+        }
+
+        @Override
+        public Binding getResponseBinding() {
+            return responseBinding;
+        }
+
+        @Override
+        public String getRequestBindingUrl() {
+            return requestBindingUrl;
+        }
+
+        public void setSignRequest(boolean signRequest) {
+            this.signRequest = signRequest;
+        }
+
+        public void setValidateResponseSignature(boolean validateResponseSignature) {
+            this.validateResponseSignature = validateResponseSignature;
+        }
+
+        public void setRequestBinding(Binding requestBinding) {
+            this.requestBinding = requestBinding;
+        }
+
+        public void setResponseBinding(Binding responseBinding) {
+            this.responseBinding = responseBinding;
+        }
+
+        public void setRequestBindingUrl(String requestBindingUrl) {
+            this.requestBindingUrl = requestBindingUrl;
+        }
+    }
+
+    public static class DefaultSingleLogoutService implements IDP.SingleLogoutService {
+        private boolean validateRequestSignature;
+        private boolean validateResponseSignature;
+        private boolean signRequest;
+        private boolean signResponse;
+        private Binding requestBinding;
+        private Binding responseBinding;
+        private String requestBindingUrl;
+        private String responseBindingUrl;
+
+        @Override
+        public boolean validateRequestSignature() {
+            return validateRequestSignature;
+        }
+
+        @Override
+        public boolean validateResponseSignature() {
+            return validateResponseSignature;
+        }
+
+        @Override
+        public boolean signRequest() {
+            return signRequest;
+        }
+
+        @Override
+        public boolean signResponse() {
+            return signResponse;
+        }
+
+         @Override
+        public Binding getRequestBinding() {
+            return requestBinding;
+        }
+
+        @Override
+        public Binding getResponseBinding() {
+            return responseBinding;
+        }
+
+        @Override
+        public String getRequestBindingUrl() {
+            return requestBindingUrl;
+        }
+
+        @Override
+        public String getResponseBindingUrl() {
+            return responseBindingUrl;
+        }
+
+        public void setValidateRequestSignature(boolean validateRequestSignature) {
+            this.validateRequestSignature = validateRequestSignature;
+        }
+
+        public void setValidateResponseSignature(boolean validateResponseSignature) {
+            this.validateResponseSignature = validateResponseSignature;
+        }
+
+        public void setSignRequest(boolean signRequest) {
+            this.signRequest = signRequest;
+        }
+
+        public void setSignResponse(boolean signResponse) {
+            this.signResponse = signResponse;
+        }
+
+        public void setRequestBinding(Binding requestBinding) {
+            this.requestBinding = requestBinding;
+        }
+
+        public void setResponseBinding(Binding responseBinding) {
+            this.responseBinding = responseBinding;
+        }
+
+        public void setRequestBindingUrl(String requestBindingUrl) {
+            this.requestBindingUrl = requestBindingUrl;
+        }
+
+        public void setResponseBindingUrl(String responseBindingUrl) {
+            this.responseBindingUrl = responseBindingUrl;
+        }
+    }
+
+
+
+    public static class DefaultIDP implements IDP {
+
+
+
+        private String entityID;
+        private PublicKey signatureValidationKey;
+        private SingleSignOnService singleSignOnService;
+        private SingleLogoutService singleLogoutService;
+
+        @Override
+        public String getEntityID() {
+            return entityID;
+        }
+
+        @Override
+        public SingleSignOnService getSingleSignOnService() {
+            return singleSignOnService;
+        }
+
+        @Override
+        public SingleLogoutService getSingleLogoutService() {
+            return singleLogoutService;
+        }
+
+        @Override
+        public PublicKey getSignatureValidationKey() {
+            return signatureValidationKey;
+        }
+
+        public void setEntityID(String entityID) {
+            this.entityID = entityID;
+        }
+
+        public void setSignatureValidationKey(PublicKey signatureValidationKey) {
+            this.signatureValidationKey = signatureValidationKey;
+        }
+
+        public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
+            this.singleSignOnService = singleSignOnService;
+        }
+
+        public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
+            this.singleLogoutService = singleLogoutService;
+        }
+    }
+
+    private IDP idp;
+    private boolean configured;
+    private SslRequired sslRequired = SslRequired.EXTERNAL;
+    private String entityID;
+    private String nameIDPolicyFormat;
+    private boolean forceAuthentication;
+    private PrivateKey decryptionKey;
+    private KeyPair signingKeyPair;
+    private String assertionConsumerServiceUrl;
+    private Set<String> roleAttributeNames;
+    private Set<String> roleFriendlyAttributeNames;
+    private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID;
+    private String principalAttributeName;
+    private String logoutPage;
+    private SignatureAlgorithm signatureAlgorithm;
+    private String signatureCanonicalizationMethod;
+
+
+    @Override
+    public IDP getIDP() {
+        return idp;
+    }
+
+    @Override
+    public boolean isConfigured() {
+        return configured;
+    }
+
+    @Override
+    public SslRequired getSslRequired() {
+        return sslRequired;
+    }
+
+    @Override
+    public String getEntityID() {
+        return entityID;
+    }
+
+    @Override
+    public String getNameIDPolicyFormat() {
+        return nameIDPolicyFormat;
+    }
+
+    @Override
+    public boolean isForceAuthentication() {
+        return forceAuthentication;
+    }
+
+    @Override
+    public PrivateKey getDecryptionKey() {
+        return decryptionKey;
+    }
+
+    @Override
+    public KeyPair getSigningKeyPair() {
+        return signingKeyPair;
+    }
+
+    @Override
+    public String getAssertionConsumerServiceUrl() {
+        return assertionConsumerServiceUrl;
+    }
+
+    @Override
+    public Set<String> getRoleAttributeNames() {
+        return roleAttributeNames;
+    }
+
+    @Override
+    public Set<String> getRoleAttributeFriendlyNames() {
+        return roleFriendlyAttributeNames;
+    }
+
+    @Override
+    public PrincipalNamePolicy getPrincipalNamePolicy() {
+        return principalNamePolicy;
+    }
+
+    @Override
+    public String getPrincipalAttributeName() {
+        return principalAttributeName;
+    }
+
+    public void setIdp(IDP idp) {
+        this.idp = idp;
+    }
+
+    public void setConfigured(boolean configured) {
+        this.configured = configured;
+    }
+
+    public void setSslRequired(SslRequired sslRequired) {
+        this.sslRequired = sslRequired;
+    }
+
+    public void setEntityID(String entityID) {
+        this.entityID = entityID;
+    }
+
+    public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
+        this.nameIDPolicyFormat = nameIDPolicyFormat;
+    }
+
+    public void setForceAuthentication(boolean forceAuthentication) {
+        this.forceAuthentication = forceAuthentication;
+    }
+
+    public void setDecryptionKey(PrivateKey decryptionKey) {
+        this.decryptionKey = decryptionKey;
+    }
+
+    public void setSigningKeyPair(KeyPair signingKeyPair) {
+        this.signingKeyPair = signingKeyPair;
+    }
+
+    public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
+        this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
+    }
+
+    public void setRoleAttributeNames(Set<String> roleAttributeNames) {
+        this.roleAttributeNames = roleAttributeNames;
+    }
+
+    public void setRoleFriendlyAttributeNames(Set<String> roleFriendlyAttributeNames) {
+        this.roleFriendlyAttributeNames = roleFriendlyAttributeNames;
+    }
+
+    public void setPrincipalNamePolicy(PrincipalNamePolicy principalNamePolicy) {
+        this.principalNamePolicy = principalNamePolicy;
+    }
+
+    public void setPrincipalAttributeName(String principalAttributeName) {
+        this.principalAttributeName = principalAttributeName;
+    }
+
+    @Override
+    public String getLogoutPage() {
+        return logoutPage;
+    }
+
+    public void setLogoutPage(String logoutPage) {
+        this.logoutPage = logoutPage;
+    }
+
+     @Override
+    public String getSignatureCanonicalizationMethod() {
+        return signatureCanonicalizationMethod;
+    }
+
+    public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
+        this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
+    }
+
+    @Override
+    public SignatureAlgorithm getSignatureAlgorithm() {
+        return signatureAlgorithm;
+    }
+
+    public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
+        this.signatureAlgorithm = signatureAlgorithm;
+    }
+}
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..1cef2b1
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
@@ -0,0 +1,91 @@
+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.getEntityID();
+            String actionUrl = deployment.getIDP().getSingleSignOnService().getRequestBindingUrl();
+            String destinationUrl = actionUrl;
+            String nameIDPolicyFormat = deployment.getNameIDPolicyFormat();
+
+            if (nameIDPolicyFormat == null) {
+                nameIDPolicyFormat =  JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+            }
+
+
+
+            SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
+                    .destination(destinationUrl)
+                    .issuer(issuerURL)
+                    .forceAuthn(deployment.isForceAuthentication())
+                    .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
+            if (deployment.getIDP().getSingleSignOnService().getResponseBinding() != null) {
+                String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
+                if (deployment.getIDP().getSingleSignOnService().getResponseBinding() == SamlDeployment.Binding.POST) {
+                    protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
+                }
+                authnRequestBuilder.protocolBinding(protocolBinding);
+
+            }
+            if (deployment.getAssertionConsumerServiceUrl() != null) {
+                authnRequestBuilder.assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl());
+            }
+            BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+
+            if (deployment.getIDP().getSingleSignOnService().signRequest()) {
+
+
+                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.getIDP().getSingleSignOnService().getRequestBinding();
+            SamlUtil.sendSaml(true, 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
new file mode 100755
index 0000000..ade3e47
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -0,0 +1,458 @@
+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.common.util.Base64;
+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.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 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() {
+
+
+        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.getEntityID())
+                .sessionIndex(account.getSessionIndex())
+                .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
+                .destination(deployment.getIDP().getSingleLogoutService().getRequestBindingUrl());
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+        if (deployment.getIDP().getSingleLogoutService().signRequest()) {
+            binding.signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+        }
+
+        binding.relayState("logout");
+
+        try {
+            SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().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;
+        String requestUri = facade.getRequest().getURI();
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            // strip out query params
+            int index = requestUri.indexOf('?');
+            if (index > -1) {
+                requestUri = requestUri.substring(0, index);
+            }
+            holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+        } else {
+            postBinding = true;
+            holder = SAMLRequestParser.parseRequestPostBinding(samlRequest);
+        }
+        RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
+        if (!requestUri.equals(requestAbstractType.getDestination().toString())) {
+            log.error("expected destination '" + requestUri + "' got '" + requestAbstractType.getDestination() + "'");
+            throw new RuntimeException("destination not equal to request.");
+        }
+
+        if (requestAbstractType instanceof LogoutRequestType) {
+            if (deployment.getIDP().getSingleLogoutService().validateRequestSignature()) {
+                validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY);
+            }
+            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.getEntityID();
+        SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+        builder.logoutRequestID(request.getID());
+        builder.destination(deployment.getIDP().getSingleLogoutService().getResponseBindingUrl());
+        builder.issuer(issuerURL);
+        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
+        if (deployment.getIDP().getSingleLogoutService().signResponse()) {
+            binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
+                    .signWith(deployment.getSigningKeyPair())
+                    .signDocument();
+            if (deployment.getSignatureCanonicalizationMethod() != null) binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+        }
+
+
+        try {
+            SamlUtil.sendSaml(false, facade, deployment.getIDP().getSingleLogoutService().getResponseBindingUrl(), binding, builder.buildDocument(),
+                    deployment.getIDP().getSingleLogoutService().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;
+        String requestUri = facade.getRequest().getURI();
+        if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+            int index = requestUri.indexOf('?');
+            if (index > -1) {
+                requestUri = requestUri.substring(0, index);
+            }
+            holder = extractRedirectBindingResponse(samlResponse);
+        } else {
+            postBinding = true;
+            holder = extractPostBindingResponse(samlResponse);
+        }
+        StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+        // validate destination
+        if (!requestUri.equals(statusResponse.getDestination())) {
+            throw new RuntimeException("destination not equal to request");
+        }
+        if (statusResponse instanceof ResponseType) {
+            if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
+                validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+            }
+            return handleLoginResponse((ResponseType)statusResponse);
+
+        } else {
+            if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) {
+                validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+            }
+            // todo need to check that it is actually a LogoutResponse
+            return handleLogoutResponse(holder, statusResponse, relayState);
+        }
+
+    }
+
+    private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) {
+        try {
+            if (postBinding) {
+                verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
+            } else {
+                verifyRedirectBindingSignature(deployment.getIDP().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.getDecryptionKey());
+            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) {
+                                    friendlyAttributes.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
+        String redirectUri = sessionStore.getRedirectUri();
+        if (redirectUri != null) {
+            facade.getResponse().setHeader("Location", redirectUri);
+            facade.getResponse().setStatus(302);
+            facade.getResponse().end();
+        } else {
+            log.debug("IDP initiated invocation");
+        }
+
+
+        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("SAML Request 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);
+            byte[] decodedSignature = Base64.decode(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/SamlConfigResolver.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java
new file mode 100755
index 0000000..2479a23
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import org.keycloak.adapters.HttpFacade.Request;
+
+/**
+ * On multi-tenant scenarios, Keycloak will defer the resolution of a
+ * SamlDeployment to the target application at the request-phase.
+ *
+ * A Request object is passed to the resolver and callers expect a complete
+ * SamlDeployment. Based on this SamlDeployment, Keycloak will resume
+ * authenticating and authorizing the request.
+ *
+ * The easiest way to build a SamlDeployment is to use
+ * DeploymentBuilder , passing the InputStream of an existing
+ * keycloak-saml.xml to the build() method.
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public interface SamlConfigResolver {
+
+    public SamlDeployment resolve(Request facade);
+
+}
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..4540b25
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -0,0 +1,78 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+
+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 static Binding parseBinding(String val) {
+            if (val == null) return POST;
+            return Binding.valueOf(val);
+        }
+    }
+
+    public interface IDP {
+        String getEntityID();
+
+        SingleSignOnService getSingleSignOnService();
+        SingleLogoutService getSingleLogoutService();
+        PublicKey getSignatureValidationKey();
+
+        public interface SingleSignOnService {
+            boolean signRequest();
+            boolean validateResponseSignature();
+            Binding getRequestBinding();
+            Binding getResponseBinding();
+            String getRequestBindingUrl();
+        }
+        public interface SingleLogoutService {
+            boolean validateRequestSignature();
+            boolean validateResponseSignature();
+            boolean signRequest();
+            boolean signResponse();
+            Binding getRequestBinding();
+            Binding getResponseBinding();
+            String getRequestBindingUrl();
+            String getResponseBindingUrl();
+        }
+    }
+
+    public IDP getIDP();
+
+    public boolean isConfigured();
+    SslRequired getSslRequired();
+    String getEntityID();
+    String getNameIDPolicyFormat();
+    boolean isForceAuthentication();
+    PrivateKey getDecryptionKey();
+    KeyPair getSigningKeyPair();
+    String getSignatureCanonicalizationMethod();
+    SignatureAlgorithm getSignatureAlgorithm();
+    String getAssertionConsumerServiceUrl();
+    String getLogoutPage();
+
+    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..cb8ea77
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
@@ -0,0 +1,19 @@
+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 {
+    private SamlDeployment deployment = null;
+
+    public SamlDeploymentContext(SamlDeployment deployment) {
+        this.deployment = deployment;
+    }
+
+    public SamlDeployment resolveDeployment(HttpFacade facade) {
+        return deployment;
+    }
+}
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..68b23da
--- /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 name;
+    }
+
+
+    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..e0ea5ec
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
@@ -0,0 +1,35 @@
+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(boolean asRequest, HttpFacade httpFacade, String actionUrl,
+                            BaseSAML2BindingBuilder binding, Document document,
+                            SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
+        if (samlBinding == SamlDeployment.Binding.POST) {
+            String html = asRequest ? binding.postBinding(document).getHtmlRequest(actionUrl) : binding.postBinding(document).getHtmlResponse(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 = asRequest ? binding.redirectBinding(document).requestURI(actionUrl).toString() : binding.redirectBinding(document).responseURI(actionUrl).toString();
+            httpFacade.getResponse().setStatus(302);
+            httpFacade.getResponse().setHeader("Location", uri);
+            httpFacade.getResponse().end();
+        }
+    }
+
+}
diff --git a/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java b/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
new file mode 100755
index 0000000..c49cdf1
--- /dev/null
+++ b/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
@@ -0,0 +1,78 @@
+package org.keycloak.test.adapters.saml;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.adapters.saml.config.parsers.KeycloakSamlAdapterXMLParser;
+
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class XmlParserTest {
+
+    @Test
+    public void testXmlParser() throws Exception {
+        InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
+        Assert.assertNotNull(is);
+        KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+        KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
+        Assert.assertNotNull(config);
+        Assert.assertEquals(1, config.getSps().size());
+        SP sp = config.getSps().get(0);
+        Assert.assertEquals("sp", sp.getEntityID());
+        Assert.assertEquals("ssl", sp.getSslPolicy());
+        Assert.assertEquals("format", sp.getNameIDPolicyFormat());
+        Assert.assertTrue(sp.isForceAuthentication());
+        Assert.assertEquals(2, sp.getKeys().size());
+        Key signing = sp.getKeys().get(0);
+        Assert.assertTrue(signing.isSigning());
+        Key.KeyStoreConfig keystore = signing.getKeystore();
+        Assert.assertNotNull(keystore);
+        Assert.assertEquals("file", keystore.getFile());
+        Assert.assertEquals("cp", keystore.getResource());
+        Assert.assertEquals("pw", keystore.getPassword());
+        Assert.assertEquals("private alias", keystore.getPrivateKeyAlias());
+        Assert.assertEquals("private pw", keystore.getPrivateKeyPassword());
+        Assert.assertEquals("cert alias", keystore.getCertificateAlias());
+        Key encryption = sp.getKeys().get(1);
+        Assert.assertTrue(encryption.isEncryption());
+        Assert.assertEquals("private pem", encryption.getPrivateKeyPem());
+        Assert.assertEquals("public pem", encryption.getPublicKeyPem());
+        Assert.assertEquals("policy", sp.getPrincipalNameMapping().getPolicy());
+        Assert.assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
+        Assert.assertTrue(sp.getRoleAttributes().size() == 1);
+        Assert.assertTrue(sp.getRoleAttributes().contains("member"));
+        Assert.assertTrue(sp.getRoleFriendlyAttributes().size() == 1);
+        Assert.assertTrue(sp.getRoleFriendlyAttributes().contains("memberOf"));
+
+        IDP idp = sp.getIdp();
+        Assert.assertEquals("idp", idp.getEntityID());
+        Assert.assertTrue(idp.getSingleSignOnService().isSignRequest());
+        Assert.assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
+        Assert.assertEquals("post", idp.getSingleSignOnService().getRequestBinding());
+        Assert.assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
+
+        Assert.assertTrue(idp.getSingleLogoutService().isSignRequest());
+        Assert.assertTrue(idp.getSingleLogoutService().isSignResponse());
+        Assert.assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
+        Assert.assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
+        Assert.assertEquals("redirect", idp.getSingleLogoutService().getRequestBinding());
+        Assert.assertEquals("post", idp.getSingleLogoutService().getResponseBinding());
+        Assert.assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
+        Assert.assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
+
+        Assert.assertTrue(idp.getKeys().size() == 1);
+        Assert.assertTrue(idp.getKeys().get(0).isSigning());
+        Assert.assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
+
+
+
+
+    }
+}
diff --git a/saml/client-adapter/core/src/test/resources/keycloak-saml.xml b/saml/client-adapter/core/src/test/resources/keycloak-saml.xml
new file mode 100755
index 0000000..242dc8e
--- /dev/null
+++ b/saml/client-adapter/core/src/test/resources/keycloak-saml.xml
@@ -0,0 +1,55 @@
+<keycloak-saml-adapter>
+    <SP entityID="sp"
+        sslPolicy="ssl"
+        nameIDPolicyFormat="format"
+        signatureAlgorithm=""
+        sgnatureCanonicalizationMethod=""
+        forceAuthentication="true">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore file="file" resource="cp" password="pw">
+                    <PrivateKey alias="private alias" password="private pw"/>
+                    <Certificate alias="cert alias"/>
+                </KeyStore>
+            </Key>
+            <Key encryption="true">
+                <PrivateKeyPem>
+                    private pem
+                </PrivateKeyPem>
+                <PublicKeyPem>
+                    public pem
+                </PublicKeyPem>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="policy" attribute="attribute"/>
+        <RoleMapping>
+            <Attribute name="member"/>
+            <FriendlyAttribute name="memberOf"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="post"
+                                 bindingUrl="url"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="redirect"
+                    responseBinding="post"
+                    postBindingUrl="posturl"
+                    redirectBindingUrl="redirecturl"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <CertificatePem>
+                        cert pem
+                    </CertificatePem>
+                </Key>
+            </Keys>
+        </IDP>
+    </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/saml/client-adapter/pom.xml b/saml/client-adapter/pom.xml
new file mode 100755
index 0000000..e1c0b71
--- /dev/null
+++ b/saml/client-adapter/pom.xml
@@ -0,0 +1,20 @@
+<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.6.0.Final-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <name>Keycloak SAML Client Adapter Modules</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-saml-client-adapter-pom</artifactId>
+    <packaging>pom</packaging>
+
+    <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..c7401b9
--- /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.6.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..9b29618
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
@@ -0,0 +1,148 @@
+/*
+ * 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 errorPage;
+
+    public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+                                String errorPage) {
+        this.deploymentContext = deploymentContext;
+        this.sessionManagement = sessionManagement;
+        this.errorPage = errorPage;
+    }
+
+    @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 = createFacade(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 = createFacade(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 = createFacade(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 (deployment.getLogoutPage() != null) {
+                redirectLogout(deployment, exchange);
+            }
+            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 void redirectLogout(SamlDeployment deployment, HttpServerExchange exchange) {
+        String page = deployment.getLogoutPage();
+        sendRedirect(exchange, page);
+        exchange.setResponseCode(302);
+        exchange.endExchange();
+    }
+
+    protected UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+        return new UndertowHttpFacade(exchange);
+    }
+
+    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/SamlServletExtension.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
new file mode 100755
index 0000000..ac3da61
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
@@ -0,0 +1,187 @@
+/*
+ * 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.AuthenticationMechanismFactory;
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.server.handlers.form.FormParserFactory;
+import io.undertow.servlet.ServletExtension;
+import io.undertow.servlet.api.AuthMethodConfig;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.LoginConfig;
+import io.undertow.servlet.api.ServletSessionConfig;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+import org.keycloak.adapters.saml.SamlConfigResolver;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+import javax.servlet.ServletContext;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlServletExtension implements ServletExtension {
+
+    protected static Logger log = Logger.getLogger(SamlServletExtension.class);
+
+    // todo when this DeploymentInfo method of the same name is fixed.
+    public boolean isAuthenticationMechanismPresent(DeploymentInfo deploymentInfo, final String mechanismName) {
+        LoginConfig loginConfig = deploymentInfo.getLoginConfig();
+        if (loginConfig != null) {
+            for (AuthMethodConfig method : loginConfig.getAuthMethods()) {
+                if (method.getName().equalsIgnoreCase(mechanismName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static InputStream getConfigInputStream(ServletContext context) {
+        InputStream is = null;
+        if (is == null) {
+            String path = context.getInitParameter("keycloak.config.file");
+            if (path == null) {
+                log.debug("using /WEB-INF/keycloak-saml.xml");
+                is = context.getResourceAsStream("/WEB-INF/keycloak-saml.xml");
+            } else {
+                try {
+                    is = new FileInputStream(path);
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return is;
+    }
+
+
+    @Override
+    @SuppressWarnings("UseSpecificCatch")
+    public void handleDeployment(DeploymentInfo deploymentInfo, final ServletContext servletContext) {
+        if (!isAuthenticationMechanismPresent(deploymentInfo, "KEYCLOAK-SAML")) {
+            log.debug("auth-method is not keycloak saml!");
+            return;
+        }
+        log.debug("SamlServletException initialization");
+
+        // Possible scenarios:
+        // 1) The deployment has a keycloak.config.resolver specified and it exists:
+        //    Outcome: adapter uses the resolver
+        // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+        //    Outcome: adapter is left unconfigured
+        // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+        //    Outcome: adapter uses it
+        // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+        //    Outcome: adapter is left unconfigured
+
+        SamlConfigResolver configResolver;
+        String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
+        SamlDeploymentContext deploymentContext = null;
+        if (configResolverClass != null) {
+            try {
+                throw new RuntimeException("Not implemented yet");
+                //configResolver = (SamlConfigResolver) deploymentInfo.getClassLoader().loadClass(configResolverClass).newInstance();
+                //deploymentContext = new AdapterDeploymentContext(configResolver);
+                //log.info("Using " + configResolverClass + " to resolve Keycloak configuration on a per-request basis.");
+            } catch (Exception ex) {
+                log.warn("The specified resolver " + configResolverClass + " could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: " + ex.getMessage());
+                //deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+            }
+        } else {
+            InputStream is = getConfigInputStream(servletContext);
+            final SamlDeployment deployment;
+            if (is == null) {
+                log.warn("No adapter configuration.  Keycloak is unconfigured and will deny all requests.");
+                deployment = new DefaultSamlDeployment();
+            } else {
+                try {
+                    ResourceLoader loader = new ResourceLoader() {
+                        @Override
+                        public InputStream getResourceAsStream(String resource) {
+                            return servletContext.getResourceAsStream(resource);
+                        }
+                    };
+                    deployment = new DeploymentBuilder().build(is, loader);
+                } catch (ParsingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            deploymentContext = new SamlDeploymentContext(deployment);
+            log.debug("Keycloak is using a per-deployment configuration.");
+        }
+
+        servletContext.setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+        UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
+        final ServletSamlAuthMech mech = new ServletSamlAuthMech(deploymentContext, userSessionManagement, getErrorPage(deploymentInfo));
+
+
+        // setup handlers
+
+        deploymentInfo.addAuthenticationMechanism("KEYCLOAK-SAML", new AuthenticationMechanismFactory() {
+            @Override
+            public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
+                return mech;
+            }
+        }); // authentication
+
+        deploymentInfo.setIdentityManager(new IdentityManager() {
+            @Override
+            public Account verify(Account account) {
+                return account;
+            }
+
+            @Override
+            public Account verify(String id, Credential credential) {
+                throw new IllegalStateException("Should never be called in Keycloak flow");
+            }
+
+            @Override
+            public Account verify(Credential credential) {
+                throw new IllegalStateException("Should never be called in Keycloak flow");
+            }
+        });
+
+        log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
+        ServletSessionConfig cookieConfig = new ServletSessionConfig();
+        cookieConfig.setPath(deploymentInfo.getContextPath());
+        deploymentInfo.setServletSessionConfig(cookieConfig);
+
+     }
+
+    protected String getErrorPage(DeploymentInfo deploymentInfo) {
+        LoginConfig loginConfig = deploymentInfo.getLoginConfig();
+        String errorPage = null;
+        if (loginConfig != null) {
+            errorPage = loginConfig.getErrorPage();
+        }
+        return errorPage;
+    }
+}
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..a5a0bd4
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
@@ -0,0 +1,71 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.Headers;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.InMemorySessionIdMapper;
+import org.keycloak.adapters.SessionIdMapper;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.ServletHttpFacade;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlAuthMech extends AbstractSamlAuthMech {
+    private SessionIdMapper idMapper = new InMemorySessionIdMapper();
+    public ServletSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
+        super(deploymentContext, sessionManagement, errorPage);
+    }
+
+    @Override
+    protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
+        return new ServletSamlSessionStore(exchange, sessionManagement, securityContext, idMapper);
+    }
+
+    @Override
+    protected UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+        return new ServletHttpFacade(exchange);
+    }
+
+    @Override
+    protected void redirectLogout(SamlDeployment deployment, HttpServerExchange exchange) {
+       servePage(exchange, deployment.getLogoutPage());
+    }
+
+    @Override
+    protected Integer servePage(HttpServerExchange exchange, String location) {
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        ServletRequest req = servletRequestContext.getServletRequest();
+        ServletResponse resp = servletRequestContext.getServletResponse();
+        RequestDispatcher disp = req.getRequestDispatcher(location);
+        //make sure the login page is never cached
+        exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
+        exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache");
+        exchange.getResponseHeaders().add(Headers.EXPIRES, "0");
+
+
+        try {
+            disp.forward(req, resp);
+        } catch (ServletException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+
+}
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..1f32666
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -0,0 +1,180 @@
+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.server.session.SessionManager;
+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.SessionIdMapper;
+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.HashSet;
+import java.util.LinkedList;
+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;
+    private final SessionIdMapper idMapper;
+
+    public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
+                                   SecurityContext securityContext,
+                                   SessionIdMapper idMapper) {
+        this.exchange = exchange;
+        this.sessionManagement = sessionManagement;
+        this.securityContext = securityContext;
+        this.idMapper = idMapper;
+    }
+
+    @Override
+    public void logoutAccount() {
+        HttpSession session = getSession(false);
+        if (session != null) {
+            SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+            if (samlSession != null) {
+                if (samlSession.getSessionIndex() != null) {
+                    idMapper.removeSession(session.getId());
+                }
+                session.removeAttribute(SamlSession.class.getName());
+            }
+            session.removeAttribute(SAML_REDIRECT_URI);
+        }
+    }
+
+    @Override
+    public void logoutByPrincipal(String principal) {
+        Set<String> sessions = idMapper.getUserSessions(principal);
+        if (sessions != null) {
+            List<String> ids = new LinkedList<>();
+            ids.addAll(sessions);
+            logoutSessionIds(ids);
+            for (String id : ids) {
+                idMapper.removeSession(id);
+            }
+        }
+
+    }
+
+    @Override
+    public void logoutBySsoId(List<String> ssoIds) {
+        if (ssoIds == null) return;
+        List<String> sessionIds = new LinkedList<>();
+        for (String id : ssoIds) {
+             String sessionId = idMapper.getSessionFromSSO(id);
+             if (sessionId != null) {
+                 sessionIds.add(sessionId);
+                 idMapper.removeSession(sessionId);
+             }
+
+        }
+        logoutSessionIds(sessionIds);
+    }
+
+    protected void logoutSessionIds(List<String> sessionIds) {
+        if (sessionIds == null || sessionIds.isEmpty()) return;
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        SessionManager sessionManager = servletRequestContext.getDeployment().getSessionManager();
+        sessionManagement.logoutHttpSessions(sessionManager, sessionIds);
+    }
+
+    @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());
+        idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
+
+    }
+
+    @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);
+
+    }
+}

saml/pom.xml 1(+1 -0)

diff --git a/saml/pom.xml b/saml/pom.xml
index 85d0080..2487e9e 100755
--- a/saml/pom.xml
+++ b/saml/pom.xml
@@ -16,5 +16,6 @@
     <modules>
         <module>saml-core</module>
         <module>saml-protocol</module>
+        <module>client-adapter</module>
     </modules>
 </project>
diff --git a/saml/saml-core/pom.xml b/saml/saml-core/pom.xml
index 7d08b3b..ec148f3 100755
--- a/saml/saml-core/pom.xml
+++ b/saml/saml-core/pom.xml
@@ -24,6 +24,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
index fb32fef..7941729 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java
@@ -105,6 +105,23 @@ public class StaxParserUtil {
     }
 
     /**
+     * Get the Attribute value
+     *
+     * @param startElement
+     * @param tag localpart of the qname of the attribute
+     *
+     * @return false if attribute not set
+     */
+    public static boolean getBooleanAttributeValue(StartElement startElement, String tag) {
+        String result = null;
+        Attribute attr = startElement.getAttributeByName(new QName(tag));
+        if (attr != null)
+            result = getAttributeValue(attr);
+        if (result == null) return false;
+        return Boolean.valueOf(result);
+    }
+
+    /**
      * Given that the {@code XMLEventReader} is in {@code XMLStreamConstants.START_ELEMENT} mode, we parse into a DOM
      * Element
      *
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..af4d1ed 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,51 @@ 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) {
+            if (privateKey == null) {
+                throw new ProcessingException("Encryptd assertion and decrypt private key is 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-core/src/main/java/org/keycloak/saml/RandomSecret.java b/saml/saml-core/src/main/java/org/keycloak/saml/RandomSecret.java
new file mode 100755
index 0000000..67f4ba7
--- /dev/null
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/RandomSecret.java
@@ -0,0 +1,25 @@
+package org.keycloak.saml;
+
+import java.security.SecureRandom;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RandomSecret {
+    /**
+     * <p>
+     * Creates a random {@code byte[]} secret of the specified size.
+     * </p>
+     *
+     * @param size the size of the secret to be created, in bytes.
+     *
+     * @return a {@code byte[]} containing the generated secret.
+     */
+    public static byte[] createRandomSecret(final int size) {
+        SecureRandom random = new SecureRandom();
+        byte[] secret = new byte[size];
+        random.nextBytes(secret);
+        return secret;
+    }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java
index 2b6a72c..8cdd302 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java
@@ -5,6 +5,7 @@ import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.services.resources.admin.RealmAuth;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
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
new file mode 100755
index 0000000..3afe813
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
@@ -0,0 +1,76 @@
+package org.keycloak.protocol.saml;
+
+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 javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2BindingBuilder> {
+    public static class PostBindingBuilder extends BasePostBindingBuilder {
+        public PostBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
+            super(builder, document);
+        }
+        public Response request(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+            return buildResponse(document, actionUrl, true);
+        }
+        public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+            return buildResponse(document, actionUrl, false);
+        }
+        protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
+            String str = builder.buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
+
+            return Response.ok(str, MediaType.TEXT_HTML_TYPE)
+                    .header("Pragma", "no-cache")
+                    .header("Cache-Control", "no-cache, no-store").build();
+        }
+
+
+    }
+
+    public static class RedirectBindingBuilder extends BaseRedirectBindingBuilder {
+        public RedirectBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
+            super(builder, document);
+        }
+
+        public Response response(String redirectUri) throws ProcessingException, ConfigurationException, IOException {
+            return response(redirectUri, false);
+        }
+
+        public Response request(String redirect) throws ProcessingException, ConfigurationException, IOException {
+            return response(redirect, true);
+        }
+
+        private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
+            URI uri = generateURI(redirectUri, asRequest);
+            if (logger.isDebugEnabled()) logger.trace("redirect-binding uri: " + uri.toString());
+            CacheControl cacheControl = new CacheControl();
+            cacheControl.setNoCache(true);
+            return Response.status(302).location(uri)
+                    .header("Pragma", "no-cache")
+                    .header("Cache-Control", "no-cache, no-store").build();
+        }
+
+    }
+
+    public RedirectBindingBuilder redirectBinding(Document document) throws ProcessingException  {
+        return new RedirectBindingBuilder(this, document);
+    }
+
+    public PostBindingBuilder postBinding(Document document) throws ProcessingException  {
+        return new PostBindingBuilder(this, document);
+    }
+
+
+
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 54dff3a..d6b745b 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -26,6 +26,11 @@ import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper;
 import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
 import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
+import org.keycloak.saml.SAML2ErrorResponseBuilder;
+import org.keycloak.saml.SAML2LoginResponseBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
@@ -166,15 +171,17 @@ public class SamlProtocol implements LoginProtocol {
 
     protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
         SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
-                .relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
                 .destination(clientSession.getRedirectUri())
                 .issuer(getResponseIssuer(realm))
                 .status(status);
       try {
+          JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
+                  .relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
+          Document document = builder.buildDocument();
           if (isPostBinding(clientSession)) {
-              return builder.postBinding().response();
+              return binding.postBinding(document).response(clientSession.getRedirectUri());
           } else {
-              return builder.redirectBinding().response();
+              return binding.redirectBinding(document).response(clientSession.getRedirectUri());
           }
         } catch (Exception e) {
             return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
@@ -336,7 +343,7 @@ public class SamlProtocol implements LoginProtocol {
             return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
         }
 
-        SAML2BindingBuilder2 bindingBuilder = new SAML2BindingBuilder2();
+        JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
         bindingBuilder.relayState(relayState);
 
         if (requiresRealmSignature(client)) {
@@ -490,12 +497,14 @@ public class SamlProtocol implements LoginProtocol {
             if (isLogoutPostBindingForClient(clientSession)) {
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
-                return logoutBuilder.postBinding().request(bindingUri);
+                JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
+                return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri);
             } else {
                 logger.debug("frontchannel redirect binding");
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
-                return logoutBuilder.redirectBinding().request(bindingUri);
+                JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
+                return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri);
             }
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
@@ -523,24 +532,25 @@ public class SamlProtocol implements LoginProtocol {
         builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
         builder.destination(logoutBindingUri);
         builder.issuer(getResponseIssuer(realm));
-        builder.relayState(logoutRelayState);
+        JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
+        binding.relayState(logoutRelayState);
         String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
         if (signingAlgorithm != null) {
             SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
             String canonicalization = userSession.getNote(SAML_LOGOUT_CANONICALIZATION);
             if (canonicalization != null) {
-                builder.canonicalizationMethod(canonicalization);
+                binding.canonicalizationMethod(canonicalization);
             }
-            builder.signatureAlgorithm(algorithm)
+            binding.signatureAlgorithm(algorithm)
                     .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
                     .signDocument();
         }
 
         try {
             if (isLogoutPostBindingForInitiator(userSession)) {
-                return builder.postBinding().response(logoutBindingUri);
+                return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
             } else {
-                return builder.redirectBinding().response(logoutBindingUri);
+                return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
             }
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
@@ -566,7 +576,8 @@ public class SamlProtocol implements LoginProtocol {
 
         String logoutRequestString = null;
         try {
-            logoutRequestString = logoutBuilder.postBinding().encoded();
+            JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
+            logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded();
         } catch (Exception e) {
             logger.warn("failed to send saml logout", e);
             return;
@@ -614,26 +625,20 @@ public class SamlProtocol implements LoginProtocol {
         SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
                                          .assertionExpiration(realm.getAccessCodeLifespan())
                                          .issuer(getResponseIssuer(realm))
+                                         .sessionIndex(clientSession.getId())
                                          .userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
                                          .destination(logoutUrl);
+        return logoutBuilder;
+    }
+
+    private JaxrsSAML2BindingBuilder createBindingBuilder(ClientModel client) {
+        JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
         if (requiresRealmSignature(client)) {
-            logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
-                         .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
-                         .signDocument();
-        }
-        /*
-        if (requiresEncryption(client)) {
-            PublicKey publicKey = null;
-            try {
-                publicKey = PemUtils.decodePublicKey(client.getAttribute(ClientModel.PUBLIC_KEY));
-            } catch (Exception e) {
-                logger.error("failed", e);
-                return;
-            }
-            logoutBuilder.encrypt(publicKey);
+            binding.signatureAlgorithm(getSignatureAlgorithm(client))
+                   .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+                   .signDocument();
         }
-        */
-        return logoutBuilder;
+        return binding;
     }
 
     @Override
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 d865b2c..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
@@ -2,6 +2,7 @@ package org.keycloak.protocol.saml;
 
 import org.keycloak.VerificationException;
 import org.keycloak.models.ClientModel;
+import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
@@ -13,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;
 
@@ -23,22 +23,6 @@ import java.security.cert.Certificate;
  */
 public class SamlProtocolUtils {
 
-    /**
-     * <p>
-     * Creates a random {@code byte[]} secret of the specified size.
-     * </p>
-     *
-     * @param size the size of the secret to be created, in bytes.
-     *
-     * @return a {@code byte[]} containing the generated secret.
-     */
-    public static byte[] createRandomSecret(final int size) {
-        SecureRandom random = new SecureRandom();
-        byte[] secret = new byte[size];
-        random.nextBytes(secret);
-        return secret;
-    }
-
 
     public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
         if (!"true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
@@ -79,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 38304e6..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
@@ -3,7 +3,6 @@ package org.keycloak.protocol.saml;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
-import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.ClientConnection;
 import org.keycloak.VerificationException;
 import org.keycloak.authentication.AuthenticationProcessor;
@@ -24,10 +23,12 @@ import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.RestartLoginCookie;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
+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.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
@@ -389,19 +390,20 @@ public class SamlService {
             builder.logoutRequestID(logoutRequest.getID());
             builder.destination(logoutBindingUri);
             builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
-            builder.relayState(logoutRelayState);
+            JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
+                    .relayState(logoutRelayState);
             if (SamlProtocol.requiresRealmSignature(client)) {
                 SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
-                builder.signatureAlgorithm(algorithm)
+                binding.signatureAlgorithm(algorithm)
                         .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
                         .signDocument();
 
             }
             try {
                 if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
-                    return builder.postBinding().response(logoutBindingUri);
+                    return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
                 } else {
-                    return builder.redirectBinding().response(logoutBindingUri);
+                    return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
                 }
             } catch (Exception e) {
                 throw new RuntimeException(e);
@@ -458,7 +460,7 @@ public class SamlService {
                 return;
             }
             PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
         }
 
 
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 9772e45..599d0b2 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -103,6 +103,14 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-adapter-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-saml-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-jaxrs-oauth-client</artifactId>
         </dependency>
         <dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlBindingTest.java
new file mode 100755
index 0000000..f51c15a
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlBindingTest.java
@@ -0,0 +1,450 @@
+package org.keycloak.testsuite.keycloaksaml;
+
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.Config;
+import org.keycloak.adapters.saml.SamlPrincipal;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
+import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
+import org.keycloak.protocol.saml.mappers.HardcodedRole;
+import org.keycloak.protocol.saml.mappers.RoleListMapper;
+import org.keycloak.protocol.saml.mappers.RoleNameMapper;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.admin.AdminRoot;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlBindingTest {
+
+    @ClassRule
+    public static SamlKeycloakRule keycloakRule = new SamlKeycloakRule() {
+        @Override
+        public void initWars() {
+             ClassLoader classLoader = SamlBindingTest.class.getClassLoader();
+
+            initializeSamlSecuredWar("/keycloak-saml/simple-post", "/sales-post",  "post.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-post", "/sales-post-sig",  "post-sig.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-post-email", "/sales-post-sig-email",  "post-sig-email.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-post-transient", "/sales-post-sig-transient",  "post-sig-transient.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-post-persistent", "/sales-post-sig-persistent",  "post-sig-persistent.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-metadata", "/sales-metadata",  "post-metadata.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-get", "/employee-sig",  "employee-sig.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/mappers", "/employee2",  "employee2.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/signed-front-get", "/employee-sig-front",  "employee-sig-front.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig",  "bad-client-post-sig.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig",  "bad-realm-post-sig.war", classLoader);
+            initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc",  "post-enc.war", classLoader);
+            uploadSP();
+            server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
+
+
+
+        }
+
+        @Override
+        public String getRealmJson() {
+            return "/keycloak-saml/testsaml.json";
+        }
+    };
+
+    public static class SamlSPFacade extends HttpServlet {
+        public static String samlResponse;
+        public static String RELAY_STATE = "http://test.com/foo/bar";
+        public static String sentRelayState;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            handler(req, resp);
+        }
+
+        @Override
+        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            handler(req, resp);
+        }
+
+        private void handler(HttpServletRequest req, HttpServletResponse resp) {
+            System.out.println("********* HERE ******");
+            if (req.getParameterMap().isEmpty()) {
+                System.out.println("redirecting");
+                resp.setStatus(302);
+                // Redirect
+                // UriBuilder builder = UriBuilder.fromUri("http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVLRTsIwFP2Vpe%2BjG4wxG0YyWYxL0BBAH3wx3XYnTbp29nYof%2B8YEvEBNOlD03vOveec2ynyWjYsae1WreC9BbTOZy0Vsr4Qk9YopjkKZIrXgMwWbJ08LNhw4LHGaKsLLcmRch3MEcFYoRVxktN1rhW2NZg1mJ0o4Gm1iMnW2oZRKnXB5VajZZEX%2BRTqRuo9ACVO2mkUih%2F4l9C8s0MNcFkjLaHW9KSUHlwR506bAnrPMam4RCBOlsYkS1%2BD3MvLcDJxAx9KN4jCkXszrG5cP%2BCVH4y8IM8PYFx2dsQOfuiILWQKLVc2JkPPH7te6HrRxh%2BzUdidwSSIXoiz%2FBZyK1Qp1Nv1yPIjCNn9ZrN0V1AKA4UlzjMY7N13IDKbHjyxXoA5291%2FtzH7I%2FApPet%2FHNawx65hli61FMXeSaTUH%2FMubtvlYU0LfcA1t5cl%2BAO%2FfxGlW%2FVQ1ipsoBCVgJLQ2XHo7385%2BwI%3D");
+                UriBuilder builder = UriBuilder.fromUri("http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVJbT8IwFP4rS99HuwluNIwEIUYSLwugD76Y2h2kSdfOng7l31uGRn0ATfrQ9HznfJfTEYpaN3zS%2Bo1ZwGsL6KP3WhvkXaEgrTPcClTIjagBuZd8Obm55mmP8cZZb6XV5NByGiwQwXllDYkmX9epNdjW4JbgtkrC%2FeK6IBvvG06ptlLojUXPc5YnFOpG2x0AJdEsaFRG7PuPoUWwQx0IXSOtoLb0SynduyLRpXUSOs8FWQuNQKL5rCDz2VO%2FymEgIY2zlJ3H%2FSx9jkU%2BzOK0ys8yNmSSsUEAYxnsqC18tyO2MDfohfEFSVkyiNlZzM5XacrDSbJePug%2Fkqj8FHKhTKXMy%2BnIng8g5FerVRmXd8sViR7AYec8AMh4tPfDO3L3Y2%2F%2F3cT4j7BH9Mf8A1nDb8PA%2Bay0WsldNNHavk1D1D5k4V0LXbi18MclJL2ke1FVvO6gvDXYgFRrBRWh4wPp7z85%2FgA%3D");
+                builder.queryParam("RelayState", RELAY_STATE);
+                resp.setHeader("Location", builder.build().toString());
+                return;
+            }
+            System.out.println("received response");
+            samlResponse = req.getParameter("SAMLResponse");
+            sentRelayState = req.getParameter("RelayState");
+        }
+    }
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+    @WebResource
+    protected WebDriver driver;
+    @WebResource
+    protected LoginPage loginPage;
+
+    protected void checkLoggedOut(String mainUrl) {
+        String pageSource = driver.getPageSource();
+        System.out.println("*** logout pagesource ***");
+        System.out.println(pageSource);
+        System.out.println("driver url: " + driver.getCurrentUrl());
+        Assert.assertTrue(pageSource.contains("request-path: /logout.jsp"));
+        driver.navigate().to(mainUrl);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+    }
+
+    //@Test
+    public void ideTesting() throws Exception {
+        Thread.sleep(100000000);
+    }
+
+    @Test
+    public void testPostSimpleLoginLogout() {
+        driver.navigate().to("http://localhost:8081/sales-post/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post/");
+        System.out.println(driver.getPageSource());
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post/");
+    }
+    @Test
+    public void testPostSimpleLoginLogoutIdpInitiated() {
+        driver.navigate().to("http://localhost:8081/auth/realms/demo/protocol/saml/clients/sales-post");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post/");
+        System.out.println(driver.getPageSource());
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post/");
+    }
+
+    @Test
+    public void testPostSignedLoginLogout() {
+        driver.navigate().to("http://localhost:8081/sales-post-sig/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/sales-post-sig?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post-sig/");
+
+    }
+    @Test
+    public void testPostSignedLoginLogoutTransientNameID() {
+        driver.navigate().to("http://localhost:8081/sales-post-sig-transient/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig-transient/");
+        System.out.println(driver.getPageSource());
+        Assert.assertFalse(driver.getPageSource().contains("bburke"));
+        Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
+        driver.navigate().to("http://localhost:8081/sales-post-sig-transient?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post-sig-transient/");
+
+    }
+    @Test
+    public void testPostSignedLoginLogoutPersistentNameID() {
+        driver.navigate().to("http://localhost:8081/sales-post-sig-persistent/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig-persistent/");
+        System.out.println(driver.getPageSource());
+        Assert.assertFalse(driver.getPageSource().contains("bburke"));
+        Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
+        driver.navigate().to("http://localhost:8081/sales-post-sig-persistent?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post-sig-persistent/");
+
+    }
+    @Test
+    public void testPostSignedLoginLogoutEmailNameID() {
+        driver.navigate().to("http://localhost:8081/sales-post-sig-email/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig-email/");
+        System.out.println(driver.getPageSource());
+        Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
+        driver.navigate().to("http://localhost:8081/sales-post-sig-email?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post-sig-email/");
+
+    }
+
+    @Test
+    public void testRelayStateEncoding() throws Exception {
+        // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look
+        // at the relay state
+        SamlSPFacade.samlResponse = null;
+        driver.navigate().to("http://localhost:8081/employee/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+        System.out.println(driver.getCurrentUrl());
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/");
+        Assert.assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
+        Assert.assertNotNull(SamlSPFacade.samlResponse);
+
+    }
+
+    @Test
+    public void testAttributes() throws Exception {
+        {
+            SamlKeycloakRule.SendUsernameServlet.sentPrincipal = null;
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = null;
+            driver.navigate().to("http://localhost:8081/employee2/");
+            Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+            List<String> requiredRoles = new LinkedList<>();
+            requiredRoles.add("manager");
+            requiredRoles.add("employee");
+            requiredRoles.add("user");
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = requiredRoles;
+            loginPage.login("bburke", "password");
+            Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee2/");
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = null;
+            SamlPrincipal principal = (SamlPrincipal)SamlKeycloakRule.SendUsernameServlet.sentPrincipal;
+            Assert.assertNotNull(principal);
+            Assert.assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
+            Assert.assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
+            Assert.assertEquals("617", principal.getAttribute("phone"));
+            Assert.assertNull(principal.getFriendlyAttribute("phone"));
+            driver.navigate().to("http://localhost:8081/employee2/?GLO=true");
+            checkLoggedOut("http://localhost:8081/employee2/");
+
+        }
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ClientModel app = appRealm.getClientByClientId("http://localhost:8081/employee2/");
+                for (ProtocolMapperModel mapper : app.getProtocolMappers()) {
+                    if (mapper.getName().equals("role-list")) {
+                        app.removeProtocolMapper(mapper);
+                        mapper.setId(null);
+                        mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true");
+                        mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf");
+                        app.addProtocolMapper(mapper);
+                    }
+                }
+                app.addProtocolMapper(HardcodedAttributeMapper.create("hardcoded-attribute", "hardcoded-attribute", "Basic", null, "hard", false, null));
+                app.addProtocolMapper(HardcodedRole.create("hardcoded-role", "hardcoded-role"));
+                app.addProtocolMapper(RoleNameMapper.create("renamed-role", "manager", "el-jefe"));
+                app.addProtocolMapper(RoleNameMapper.create("renamed-employee-role", "http://localhost:8081/employee/.employee", "pee-on"));
+            }
+        }, "demo");
+
+        System.out.println(">>>>>>>>>> single role attribute <<<<<<<<");
+
+        {
+            SamlKeycloakRule.SendUsernameServlet.sentPrincipal = null;
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = null;
+            driver.navigate().to("http://localhost:8081/employee2/");
+            Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+            List<String> requiredRoles = new LinkedList<>();
+            requiredRoles.add("el-jefe");
+            requiredRoles.add("user");
+            requiredRoles.add("hardcoded-role");
+            requiredRoles.add("pee-on");
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = requiredRoles;
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = requiredRoles;
+            loginPage.login("bburke", "password");
+            Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee2/");
+            SamlKeycloakRule.SendUsernameServlet.checkRoles = null;
+            SamlPrincipal principal = (SamlPrincipal)SamlKeycloakRule.SendUsernameServlet.sentPrincipal;
+            Assert.assertNotNull(principal);
+            Assert.assertEquals("hard", principal.getAttribute("hardcoded-attribute"));
+
+
+        }
+    }
+
+    @Test
+    public void testRedirectSignedLoginLogout() {
+        driver.navigate().to("http://localhost:8081/employee-sig/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee-sig/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/employee-sig?GLO=true");
+        checkLoggedOut("http://localhost:8081/employee-sig/");
+
+    }
+
+    @Test
+    public void testRedirectSignedLoginLogoutFrontNoSSO() {
+        driver.navigate().to("http://localhost:8081/employee-sig-front/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee-sig-front/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/employee-sig-front?GLO=true");
+        checkLoggedOut("http://localhost:8081/employee-sig-front/");
+
+    }
+
+    @Test
+    public void testRedirectSignedLoginLogoutFront() {
+        // visit 1st app an logg in
+        System.out.println("visit 1st app ");
+        driver.navigate().to("http://localhost:8081/employee-sig/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+        System.out.println("login to form");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee-sig/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+
+        // visit 2nd app
+        System.out.println("visit 2nd app ");
+        driver.navigate().to("http://localhost:8081/employee-sig-front/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee-sig-front/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+
+        // visit 3rd app
+        System.out.println("visit 3rd app ");
+        driver.navigate().to("http://localhost:8081/sales-post-sig/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+
+        // logout of first app
+        System.out.println("GLO");
+        driver.navigate().to("http://localhost:8081/employee-sig?GLO=true");
+        checkLoggedOut("http://localhost:8081/employee-sig/");
+        driver.navigate().to("http://localhost:8081/employee-sig-front/");
+        String currentUrl = driver.getCurrentUrl();
+        Assert.assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+        driver.navigate().to("http://localhost:8081/sales-post-sig/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
+
+    }
+
+    @Test
+    public void testPostEncryptedLoginLogout() {
+        driver.navigate().to("http://localhost:8081/sales-post-enc/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-enc/");
+        Assert.assertTrue(driver.getPageSource().contains("bburke"));
+        driver.navigate().to("http://localhost:8081/sales-post-enc?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-post-enc/");
+
+    }
+    @Test
+    public void testPostBadClientSignature() {
+        driver.navigate().to("http://localhost:8081/bad-client-sales-post-sig/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        Assert.assertEquals(driver.getTitle(), "We're sorry...");
+
+    }
+
+    @Test
+    public void testPostBadRealmSignature() {
+        driver.navigate().to("http://localhost:8081/bad-realm-sales-post-sig/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/bad-realm-sales-post-sig/");
+        Assert.assertTrue(driver.getPageSource().contains("null"));
+    }
+
+    private static String createToken() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+
+            RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
+            ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
+            TokenManager tm = new TokenManager();
+            UserModel admin = session.users().getUserByUsername("admin", adminRealm);
+            ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
+            clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
+            UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
+            AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
+            return tm.encodeToken(adminRealm, token);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+
+    @Test
+    public void testMetadataPostSignedLoginLogout() throws Exception {
+
+        driver.navigate().to("http://localhost:8081/sales-metadata/");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
+        loginPage.login("bburke", "password");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-metadata/");
+        String pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("bburke"));
+        driver.navigate().to("http://localhost:8081/sales-metadata?GLO=true");
+        checkLoggedOut("http://localhost:8081/sales-metadata/");
+
+    }
+
+    public static void uploadSP() {
+        String token = createToken();
+        final String authHeader = "Bearer " + token;
+        ClientRequestFilter authFilter = new ClientRequestFilter() {
+            @Override
+            public void filter(ClientRequestContext requestContext) throws IOException {
+                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+            }
+        };
+        Client client = ClientBuilder.newBuilder().register(authFilter).build();
+        UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
+        WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
+
+
+        MultipartFormDataOutput formData = new MultipartFormDataOutput();
+        InputStream is = SamlBindingTest.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml");
+        Assert.assertNotNull(is);
+        formData.addFormData("file", is, MediaType.APPLICATION_XML_TYPE);
+
+        WebTarget upload = adminRealms.path("demo/client-importers/saml2-entity-descriptor/upload");
+        System.out.println(upload.getUri());
+        Response response = upload.request().post(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA));
+        Assert.assertEquals(204, response.getStatus());
+        response.close();
+        client.close();
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java
new file mode 100755
index 0000000..e7889af
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlKeycloakRule.java
@@ -0,0 +1,190 @@
+package org.keycloak.testsuite.keycloaksaml;
+
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.server.handlers.resource.Resource;
+import io.undertow.server.handlers.resource.ResourceChangeListener;
+import io.undertow.server.handlers.resource.ResourceManager;
+import io.undertow.server.handlers.resource.URLResource;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.LoginConfig;
+import io.undertow.servlet.api.SecurityConstraint;
+import io.undertow.servlet.api.ServletInfo;
+import io.undertow.servlet.api.WebResourceCollection;
+import org.junit.Assert;
+import org.keycloak.adapters.saml.undertow.SamlServletExtension;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.picketlink.identity.federation.bindings.wildfly.sp.SPServletExtension;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
+
+    public static class SendUsernameServlet extends HttpServlet {
+
+        public static Principal sentPrincipal;
+        public static List<String> checkRoles;
+
+        @Override
+        protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            if (checkRoles != null) {
+                for (String role : checkRoles) {
+                    System.out.println("check role: " + role);
+                    Assert.assertTrue(req.isUserInRole(role));
+                }
+
+            }
+            resp.setContentType("text/plain");
+            OutputStream stream = resp.getOutputStream();
+            Principal principal = req.getUserPrincipal();
+            stream.write("request-path: ".getBytes());
+            stream.write(req.getPathInfo().getBytes());
+            stream.write("\n".getBytes());
+            stream.write("principal=".getBytes());
+            if (principal == null) {
+                stream.write("null".getBytes());
+                return;
+            }
+            String name = principal.getName();
+            stream.write(name.getBytes());
+            sentPrincipal = principal;
+
+        }
+        @Override
+        protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            if (checkRoles != null) {
+                for (String role : checkRoles) {
+                    Assert.assertTrue(req.isUserInRole(role));
+                }
+
+            }
+            resp.setContentType("text/plain");
+            OutputStream stream = resp.getOutputStream();
+            Principal principal = req.getUserPrincipal();
+            stream.write("request-path: ".getBytes());
+            stream.write(req.getPathInfo().getBytes());
+            stream.write("\n".getBytes());
+            stream.write("principal=".getBytes());
+            if (principal == null) {
+                stream.write("null".getBytes());
+                return;
+            }
+            String name = principal.getName();
+            stream.write(name.getBytes());
+            sentPrincipal = principal;
+        }
+    }
+
+    public static class TestResourceManager implements ResourceManager {
+
+        private final String basePath;
+
+        public TestResourceManager(String basePath){
+            this.basePath = basePath;
+        }
+
+        @Override
+        public Resource getResource(String path) throws IOException {
+            String temp = path;
+            String fullPath = basePath + temp;
+            URL url = getClass().getResource(fullPath);
+            if (url == null) {
+                System.out.println("url is null: " + fullPath);
+            }
+            return new URLResource(url, url.openConnection(), path);
+        }
+
+        @Override
+        public boolean isResourceChangeListenerSupported() {
+            throw new RuntimeException();
+        }
+
+        @Override
+        public void registerResourceChangeListener(ResourceChangeListener listener) {
+            throw new RuntimeException();
+        }
+
+        @Override
+        public void removeResourceChangeListener(ResourceChangeListener listener) {
+            throw new RuntimeException();
+        }
+
+        @Override
+        public void close() throws IOException {
+            throw new RuntimeException();
+        }
+    }
+
+    public static class TestIdentityManager implements IdentityManager {
+        @Override
+        public Account verify(Account account) {
+            return account;
+        }
+
+        @Override
+        public Account verify(String userName, Credential credential) {
+            throw new RuntimeException("WTF");
+        }
+
+        @Override
+        public Account verify(Credential credential) {
+            throw new RuntimeException();
+        }
+    }
+
+    @Override
+    protected void setupKeycloak() {
+        String realmJson = getRealmJson();
+        server.importRealm(getClass().getResourceAsStream(realmJson));
+        initWars();
+    }
+
+    public abstract void initWars();
+
+    public void initializeSamlSecuredWar(String warResourcePath, String contextPath, String warDeploymentName, ClassLoader classLoader) {
+
+        ServletInfo regularServletInfo = new ServletInfo("servlet", SendUsernameServlet.class)
+                .addMapping("/*");
+
+        SecurityConstraint constraint = new SecurityConstraint();
+        WebResourceCollection collection = new WebResourceCollection();
+        collection.addUrlPattern("/*");
+        constraint.addWebResourceCollection(collection);
+        constraint.addRoleAllowed("manager");
+        constraint.addRoleAllowed("el-jefe");
+        LoginConfig loginConfig = new LoginConfig("KEYCLOAK-SAML", "Test Realm");
+
+        ResourceManager resourceManager = new TestResourceManager(warResourcePath);
+
+        DeploymentInfo deploymentInfo = new DeploymentInfo()
+                .setClassLoader(classLoader)
+                .setIdentityManager(new TestIdentityManager())
+                .setContextPath(contextPath)
+                .setDeploymentName(warDeploymentName)
+                .setLoginConfig(loginConfig)
+                .setResourceManager(resourceManager)
+                .addServlets(regularServletInfo)
+                .addSecurityConstraint(constraint)
+                .addServletExtension(new SamlServletExtension());
+        server.getServer().deploy(deploymentInfo);
+    }
+
+    public String getRealmJson() {
+        return "/keycloak-saml/testsaml.json";
+    }
+
+
+}
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..f5b05df
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/bad-client-sales-post-sig/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8081/bad-client-sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8081/bad-client-sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keystore.jks
new file mode 100755
index 0000000..6a3e3ba
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..2f7c581
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/bad-realm-sales-post-sig/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8081/bad-realm-sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8081/bad-realm-sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keystore.jks
new file mode 100755
index 0000000..215384c
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..c892ec7
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post-enc/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" encryption="true">
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true" >
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keystore.jks
new file mode 100755
index 0000000..822162c
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..caaff5c
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,25 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/employee2/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="memberOf"/>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..51c8e0c
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,44 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/employee-sig-front/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keystore.jks
new file mode 100755
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..909216d
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,44 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/employee-sig/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keystore.jks
new file mode 100755
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..9a57c85
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-metadata/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..0a907ce
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post-sig/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..ef9856a
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,44 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post-sig-email/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..7bc05f6
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post-sig-persistent/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..5d614fa
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,45 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post-sig-transient/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/sales-post-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/sales-post-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                        <Certificate alias="demo"/>
+                    </KeyStore>
+                </Key>
+            </Keys>
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keystore.jks
new file mode 100755
index 0000000..144830b
Binary files /dev/null and b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..501e8f5
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,24 @@
+<keycloak-saml-adapter>
+    <SP entityID="http://localhost:8081/sales-post/"
+        sslPolicy="EXTERNAL"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        logoutPage="/logout.jsp"
+        forceAuthentication="false">
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleMapping>
+            <Attribute name="Role"/>
+        </RoleMapping>
+        <IDP entityID="idp">
+            <SingleSignOnService requestBinding="POST"
+                                 bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/sp-metadata.xml b/testsuite/integration/src/test/resources/keycloak-saml/sp-metadata.xml
new file mode 100755
index 0000000..9b8b899
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/sp-metadata.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntitiesDescriptor Name="urn:mace:shibboleth:testshib:two"
+                    xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+        >
+    <EntityDescriptor entityID="http://localhost:8081/sales-metadata/">
+        <SPSSODescriptor AuthnRequestsSigned="true"
+                protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext">
+            <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+            </NameIDFormat>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/sales-metadata/"/>
+            <AssertionConsumerService
+                    Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/sales-metadata/"
+                    index="1" isDefault="true" />
+            <KeyDescriptor use="signing">
+                <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+                    <dsig:X509Data>
+                        <dsig:X509Certificate>
+                            MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw==
+                        </dsig:X509Certificate>
+                    </dsig:X509Data>
+                </dsig:KeyInfo>
+            </KeyDescriptor>
+        </SPSSODescriptor>
+        <Organization>
+            <OrganizationName xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                              xml:lang="en">JBoss</OrganizationName>
+            <OrganizationDisplayName xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                                     xml:lang="en">JBoss by Red Hat</OrganizationDisplayName>
+            <OrganizationURL xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                             xml:lang="en">http://localhost:8080/sales-metadata/</OrganizationURL>
+        </Organization>
+        <ContactPerson contactType="technical">
+            <GivenName>The</GivenName>
+            <SurName>Admin</SurName>
+            <EmailAddress>admin@mycompany.com</EmailAddress>
+        </ContactPerson>
+    </EntityDescriptor>
+</EntitiesDescriptor>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
new file mode 100755
index 0000000..5fafa8e
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
@@ -0,0 +1,367 @@
+{
+    "id": "demo",
+    "realm": "demo",
+    "enabled": true,
+    "sslRequired": "external",
+    "registrationAllowed": true,
+    "resetPasswordAllowed": true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "defaultRoles": [ "user" ],
+    "smtpServer": {
+        "from": "auto@keycloak.org",
+        "host": "localhost",
+        "port":"3025"
+    },
+    "users" : [
+        {
+            "username" : "bburke",
+            "enabled": true,
+            "email" : "bburke@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                  "value" : "password" }
+            ],
+            "attributes" : {
+                "phone": "617"
+            },
+            "realmRoles": ["manager", "user"],
+            "applicationRoles": {
+                "http://localhost:8081/employee/": [ "employee" ],
+                "http://localhost:8081/employee2/": [ "employee" ]
+            }
+        }
+    ],
+    "applications": [
+        {
+            "name": "http://localhost:8081/sales-post/",
+            "enabled": true,
+            "fullScopeAllowed": true,
+            "protocol": "saml",
+            "baseUrl": "http://localhost:8081/sales-post",
+            "redirectUris": [
+                "http://localhost:8081/sales-post/*"
+            ],
+            "attributes": {
+                "saml.authnstatement": "true",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post/",
+                "saml_idp_initiated_sso_url_name": "sales-post"
+            }
+        },
+        {
+            "name": "http://localhost:8081/sales-post-sig/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/sales-post-sig",
+            "redirectUris": [
+                "http://localhost:8081/sales-post-sig/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/sales-post-sig-transient/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/sales-post-sig-transient",
+            "adminUrl": "http://localhost:8081/sales-post-sig-transient",
+            "redirectUris": [
+                "http://localhost:8081/sales-post-sig-transient/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/sales-post-sig-persistent/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/sales-post-sig-persistent",
+            "redirectUris": [
+                "http://localhost:8081/sales-post-sig-persistent/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/sales-post-sig-email/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/sales-post-sig-email",
+            "adminUrl": "http://localhost:8081/sales-post-sig-email",
+            "redirectUris": [
+                "http://localhost:8081/sales-post-sig-email/*"
+            ],
+            "attributes": {
+                "saml_force_name_id_format": "true",
+                "saml_name_id_format": "email",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-email/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/bad-realm-sales-post-sig/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
+            "adminUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
+            "redirectUris": [
+                "http://localhost:8081/bad-realm-sales-post-sig/*"
+            ],
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/bad-client-sales-post-sig/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/bad-client-sales-post-sig/",
+            "adminUrl": "http://localhost:8081/bad-client-sales-post-sig/",
+            "redirectUris": [
+                "http://localhost:8081/bad-client-sales-post-sig/*"
+            ],
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/sales-post-enc/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/sales-post-enc",
+            "redirectUris": [
+                "http://localhost:8081/sales-post-enc/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-enc/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-enc/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-enc/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-enc/",
+                "saml.server.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA512",
+                "saml.client.signature": "true",
+                "saml.encrypt": "true",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
+                "saml.encryption.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
+            }
+        },
+        {
+            "name": "http://localhost:8081/employee-sig/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/employee-sig",
+            "redirectUris": [
+                "http://localhost:8081/employee-sig/*"
+            ],
+            "adminUrl": "http://localhost:8081/employee-sig/",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
+            "name": "http://localhost:8081/employee/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/employee/",
+            "redirectUris": [
+                "http://localhost:8081/employee/*"
+            ],
+            "adminUrl": "http://localhost:8081/employee/",
+            "attributes": {
+                "saml.authnstatement": "true"
+            },
+            "protocolMappers": [
+                {
+                    "name": "email",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-property-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "email",
+                        "friendly.name": "email",
+                        "attribute.name": "urn:oid:1.2.840.113549.1.9.1",
+                        "attribute.nameformat": "URI Reference"
+                    }
+                },
+                {
+                    "name": "phone",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-attribute-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "phone",
+                        "attribute.name": "phone",
+                        "attribute.nameformat": "Basic"
+                    }
+                },
+                {
+                    "name": "role-list",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-role-list-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "attribute.name": "Role",
+                        "attribute.nameformat": "Basic",
+                        "single": "false"
+                    }
+                }
+            ]
+        },
+        {
+            "name": "http://localhost:8081/employee2/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8081/employee2/",
+            "redirectUris": [
+                "http://localhost:8081/employee2/*"
+            ],
+            "adminUrl": "http://localhost:8081/employee2/",
+            "attributes": {
+                "saml.authnstatement": "true"
+            },
+            "protocolMappers": [
+                {
+                    "name": "email",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-property-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "email",
+                        "friendly.name": "email",
+                        "attribute.name": "urn:oid:1.2.840.113549.1.9.1",
+                        "attribute.nameformat": "URI Reference"
+                    }
+                },
+                {
+                    "name": "phone",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-user-attribute-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "user.attribute": "phone",
+                        "attribute.name": "phone",
+                        "attribute.nameformat": "Basic"
+                    }
+                },
+                {
+                    "name": "role-list",
+                    "protocol": "saml",
+                    "protocolMapper": "saml-role-list-mapper",
+                    "consentRequired": false,
+                    "config": {
+                        "attribute.name": "Role",
+                        "attribute.nameformat": "Basic",
+                        "single": "false"
+                    }
+                }
+            ]
+        },
+        {
+            "name": "http://localhost:8081/employee-sig-front/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "frontchannelLogout": true,
+            "baseUrl": "http://localhost:8081/employee-sig-front/",
+            "redirectUris": [
+                "http://localhost:8081/employee-sig-front/*"
+            ],
+            "attributes": {
+                "saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig-front/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig-front/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/employee-sig-front/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig-front/",
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA1",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "manager",
+                "description": "Have Manager privileges"
+            },
+            {
+                "name": "user",
+                "description": "Have User privileges"
+            }
+        ],
+        "application" : {
+            "http://localhost:8081/employee/" : [
+                {
+                    "name": "employee",
+                    "description": "Have Employee privileges"
+                }
+            ],
+            "http://localhost:8081/employee2/" : [
+                {
+                    "name": "employee",
+                    "description": "Have Employee privileges"
+                }
+            ]
+        }
+    }
+}
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index 4db2adf..e929c24 100755
--- a/testsuite/integration/src/test/resources/saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/saml/testsaml.json
@@ -69,7 +69,6 @@
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
             }
         },
@@ -92,7 +91,6 @@
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
             }
         },
@@ -114,7 +112,6 @@
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
             }
         },
@@ -139,7 +136,6 @@
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
             }
         },
@@ -157,7 +153,6 @@
                 "saml.server.signature": "true",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
             }
         },
@@ -175,7 +170,6 @@
                 "saml.server.signature": "true",
                 "saml.client.signature": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
             }
         },
@@ -198,9 +192,7 @@
                 "saml.client.signature": "true",
                 "saml.encrypt": "true",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t",
                 "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
-                "saml.encryption.private.key": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t",
                 "saml.encryption.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
             }
         },
@@ -219,7 +211,6 @@
                 "saml.client.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA1",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICXQIBAAKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABAoGANU1efgc6ojIvwn7Lsf8GAKN9z2D6uS0T3I9nw1k2CtI+xWhgKAUltEANx5lEfBRYIdYclidRpqrk8DYgzASrDYTHXzqVBJfAk1VrAGpqyRq+TNMLUHkXiTiSDOQ6WqhX93UGMmAgQm1RsLa6+fy1BO/B2y85+Yf2OUylsKS6avECQQDslRDiNFdtEjdvyOL20tQ7+W+eKVxVxKAyQ3gFjIIDizELZt+Jq1Wz6XV9NhK1JFtlVugeD1tlW/+K16fEmDYXAkEAzqKoN/JeGb20rfQldAUWdQbb0jrQAYlgoSU/9fYH9YVJT8vnkfhPBTwIw9H9euf1//lRP/jHltHd5ch4230YyQJBAN3rOkoltPiABPZbpuLGgwS7BwOCYrWlWmurtBLoaTCvyVKbrgXybNL1pBrOtR+rufvGWLeRyja65Gs1vY6BBQMCQQCTsNq/MjJj/522f7yNUl2cw4w2lOa7Um+IflFbAcDqkZu2ty0Kvgns2d4B6INeZ5ECpjaWnMA7YkFRzZnkd2NRAkB8lEY56ScnNigoZkkjtEUd2ejdhZPYuS9SKfv9zHwN+I+DE2vVFZz8GPq/iLcMx13PkZaYaJNQ4FtQY/hRLSn5",
                 "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
             }
         },
@@ -292,7 +283,6 @@
                 "saml.client.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA1",
                 "saml.authnstatement": "true",
-                "saml.signing.private.key": "MIICXQIBAAKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABAoGANU1efgc6ojIvwn7Lsf8GAKN9z2D6uS0T3I9nw1k2CtI+xWhgKAUltEANx5lEfBRYIdYclidRpqrk8DYgzASrDYTHXzqVBJfAk1VrAGpqyRq+TNMLUHkXiTiSDOQ6WqhX93UGMmAgQm1RsLa6+fy1BO/B2y85+Yf2OUylsKS6avECQQDslRDiNFdtEjdvyOL20tQ7+W+eKVxVxKAyQ3gFjIIDizELZt+Jq1Wz6XV9NhK1JFtlVugeD1tlW/+K16fEmDYXAkEAzqKoN/JeGb20rfQldAUWdQbb0jrQAYlgoSU/9fYH9YVJT8vnkfhPBTwIw9H9euf1//lRP/jHltHd5ch4230YyQJBAN3rOkoltPiABPZbpuLGgwS7BwOCYrWlWmurtBLoaTCvyVKbrgXybNL1pBrOtR+rufvGWLeRyja65Gs1vY6BBQMCQQCTsNq/MjJj/522f7yNUl2cw4w2lOa7Um+IflFbAcDqkZu2ty0Kvgns2d4B6INeZ5ECpjaWnMA7YkFRzZnkd2NRAkB8lEY56ScnNigoZkkjtEUd2ejdhZPYuS9SKfv9zHwN+I+DE2vVFZz8GPq/iLcMx13PkZaYaJNQ4FtQY/hRLSn5",
                 "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
             }
         }