keycloak-aplcache

Changes

core/pom.xml 5(+0 -5)

distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/net/iharder/base64/main/module.xml 13(+0 -13)

distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/net/iharder/base64/main/module.xml 13(+0 -13)

pom.xml 36(+30 -6)

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;
     }
+
 }

core/pom.xml 5(+0 -5)

diff --git a/core/pom.xml b/core/pom.xml
index 7ea016a..694beb7 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,7 +21,6 @@
             org.keycloak.*
         </keycloak.osgi.export>
         <keycloak.osgi.import>
-            net.iharder;version=${base64.version},
             *;resolution:=optional
         </keycloak.osgi.import>
     </properties>
@@ -35,10 +34,6 @@
             <artifactId>bcpkix-jdk15on</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-core-asl</artifactId>
         </dependency>
diff --git a/core/src/main/java/org/keycloak/util/Base64.java b/core/src/main/java/org/keycloak/util/Base64.java
new file mode 100644
index 0000000..ee83793
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/Base64.java
@@ -0,0 +1,2065 @@
+package org.keycloak.util;
+
+/**
+ * <p>Encodes and decodes to and from Base64 notation.</p>
+ * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <code>String encoded = Base64.encode( myByteArray );</code>
+ * <br />
+ * <code>byte[] myByteArray = Base64.decode( encoded );</code>
+ *
+ * <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass 
+ * several pieces of information to the encoder. In the "higher level" methods such as 
+ * encodeBytes( bytes, options ) the options parameter can be used to indicate such 
+ * things as first gzipping the bytes before encoding them, not inserting linefeeds,
+ * and encoding using the URL-safe and Ordered dialects.</p>
+ *
+ * <p>Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>,
+ * Section 2.1, implementations should not add line feeds unless explicitly told
+ * to do so. I've got Base64 set to this behavior now, although earlier versions
+ * broke lines by default.</p>
+ *
+ * <p>The constants defined in Base64 can be OR-ed together to combine options, so you 
+ * might make a call like this:</p>
+ *
+ * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code>
+ * <p>to compress the data before encoding it and then making the output have newline characters.</p>
+ * <p>Also...</p>
+ * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code>
+ *
+ *
+ *
+ * <p>
+ * Change Log:
+ * </p>
+ * <ul>
+ *  <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the
+ *   value 01111111, which is an invalid base 64 character but should not
+ *   throw an ArrayIndexOutOfBoundsException either. Led to discovery of
+ *   mishandling (or potential for better handling) of other bad input
+ *   characters. You should now get an IOException if you try decoding
+ *   something that has bad characters in it.</li>
+ *  <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded
+ *   string ended in the last column; the buffer was not properly shrunk and
+ *   contained an extra (null) byte that made it into the string.</li>
+ *  <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size
+ *   was wrong for files of size 31, 34, and 37 bytes.</li>
+ *  <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing
+ *   the Base64.OutputStream closed the Base64 encoding (by padding with equals
+ *   signs) too soon. Also added an option to suppress the automatic decoding
+ *   of gzipped streams. Also added experimental support for specifying a
+ *   class loader when using the
+ *   {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)}
+ *   method.</li>
+ *  <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java
+ *   footprint with its CharEncoders and so forth. Fixed some javadocs that were
+ *   inconsistent. Removed imports and specified things like java.io.IOException
+ *   explicitly inline.</li>
+ *  <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the
+ *   final encoded data will be so that the code doesn't have to create two output
+ *   arrays: an oversized initial one and then a final, exact-sized one. Big win
+ *   when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not
+ *   using the gzip options which uses a different mechanism with streams and stuff).</li>
+ *  <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some
+ *   similar helper methods to be more efficient with memory by not returning a
+ *   String but just a byte array.</li>
+ *  <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments
+ *   and bug fixes queued up and finally executed. Thanks to everyone who sent
+ *   me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else.
+ *   Much bad coding was cleaned up including throwing exceptions where necessary 
+ *   instead of returning null values or something similar. Here are some changes
+ *   that may affect you:
+ *   <ul>
+ *    <li><em>Does not break lines, by default.</em> This is to keep in compliance with
+ *      <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li>
+ *    <li><em>Throws exceptions instead of returning null values.</em> Because some operations
+ *      (especially those that may permit the GZIP option) use IO streams, there
+ *      is a possiblity of an java.io.IOException being thrown. After some discussion and
+ *      thought, I've changed the behavior of the methods to throw java.io.IOExceptions
+ *      rather than return null if ever there's an error. I think this is more
+ *      appropriate, though it will require some changes to your code. Sorry,
+ *      it should have been done this way to begin with.</li>
+ *    <li><em>Removed all references to System.out, System.err, and the like.</em>
+ *      Shame on me. All I can say is sorry they were ever there.</li>
+ *    <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed
+ *      such as when passed arrays are null or offsets are invalid.</li>
+ *    <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings.
+ *      This was especially annoying before for people who were thorough in their
+ *      own projects and then had gobs of javadoc warnings on this file.</li>
+ *   </ul>
+ *  <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug
+ *   when using very small files (~&lt; 40 bytes).</li>
+ *  <li>v2.2 - Added some helper methods for encoding/decoding directly from
+ *   one file to the next. Also added a main() method to support command line
+ *   encoding/decoding from one file to the next. Also added these Base64 dialects:
+ *   <ol>
+ *   <li>The default is RFC3548 format.</li>
+ *   <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
+ *   URL and file name friendly format as described in Section 4 of RFC3548.
+ *   http://www.faqs.org/rfcs/rfc3548.html</li>
+ *   <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
+ *   URL and file name friendly format that preserves lexical ordering as described
+ *   in http://www.faqs.org/qa/rfcc-1940.html</li>
+ *   </ol>
+ *   Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a>
+ *   for contributing the new Base64 dialects.
+ *  </li>
+ *
+ *  <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
+ *   some convenience methods for reading and writing to and from files.</li>
+ *  <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
+ *   with other encodings (like EBCDIC).</li>
+ *  <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
+ *   encoded data was a single byte.</li>
+ *  <li>v2.0 - I got rid of methods that used booleans to set options. 
+ *   Now everything is more consolidated and cleaner. The code now detects
+ *   when data that's being decoded is gzip-compressed and will decompress it
+ *   automatically. Generally things are cleaner. You'll probably have to
+ *   change some method calls that you were making to support the new
+ *   options format (<tt>int</tt>s that you "OR" together).</li>
+ *  <li>v1.5.1 - Fixed bug when decompressing and decoding to a             
+ *   byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.      
+ *   Added the ability to "suspend" encoding in the Output Stream so        
+ *   you can turn on and off the encoding if you need to embed base64       
+ *   data in an otherwise "normal" stream (like an XML file).</li>  
+ *  <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
+ *      This helps when using GZIP streams.
+ *      Added the ability to GZip-compress objects before encoding them.</li>
+ *  <li>v1.4 - Added helper methods to read/write files.</li>
+ *  <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
+ *  <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
+ *      where last buffer being read, if not completely full, was not returned.</li>
+ *  <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
+ *  <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
+ * </ul>
+ *
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.3.7
+ */
+public class Base64
+{
+    
+/* ********  P U B L I C   F I E L D S  ******** */
+
+
+    /** No options specified. Value is zero. */
+    public final static int NO_OPTIONS = 0;
+
+    /** Specify encoding in first bit. Value is one. */
+    public final static int ENCODE = 1;
+
+
+    /** Specify decoding in first bit. Value is zero. */
+    public final static int DECODE = 0;
+
+
+    /** Specify that data should be gzip-compressed in second bit. Value is two. */
+    public final static int GZIP = 2;
+
+    /** Specify that gzipped data should <em>not</em> be automatically gunzipped. */
+    public final static int DONT_GUNZIP = 4;
+
+
+    /** Do break lines when encoding. Value is 8. */
+    public final static int DO_BREAK_LINES = 8;
+
+    /**
+     * Encode using Base64-like encoding that is URL- and Filename-safe as described
+     * in Section 4 of RFC3548: 
+     * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+     * It is important to note that data encoded this way is <em>not</em> officially valid Base64, 
+     * or at the very least should not be called Base64 without also specifying that is
+     * was encoded using the URL- and Filename-safe dialect.
+     */
+    public final static int URL_SAFE = 16;
+
+
+    /**
+     * Encode using the special "ordered" dialect of Base64 described here:
+     * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+     */
+    public final static int ORDERED = 32;
+    
+    
+/* ********  P R I V A T E   F I E L D S  ******** */
+
+
+    /** Maximum line length (76) of Base64 output. */
+    private final static int MAX_LINE_LENGTH = 76;
+
+
+    /** The equals sign (=) as a byte. */
+    private final static byte EQUALS_SIGN = (byte)'=';
+
+
+    /** The new line character (\n) as a byte. */
+    private final static byte NEW_LINE = (byte)'\n';
+
+
+    /** Preferred encoding. */
+    private final static String PREFERRED_ENCODING = "US-ASCII";
+
+
+    private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+    private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+	
+	
+/* ********  S T A N D A R D   B A S E 6 4   A L P H A B E T  ******** */
+
+    /** The 64 valid Base64 values. */
+    /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
+    private final static byte[] _STANDARD_ALPHABET = {
+            (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+            (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+            (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+            (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+            (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
+    };
+
+
+    /**
+     * Translates a Base64 value to either its 6-bit reconstruction value
+     * or a negative number indicating some other meaning.
+     **/
+    private final static byte[] _STANDARD_DECODABET = {
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
+            -5,-5,                                      // Whitespace: Tab and Linefeed
+            -9,-9,                                      // Decimal 11 - 12
+            -5,                                         // Whitespace: Carriage Return
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
+            -9,-9,-9,-9,-9,                             // Decimal 27 - 31
+            -5,                                         // Whitespace: Space
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
+            62,                                         // Plus sign at decimal 43
+            -9,-9,-9,                                   // Decimal 44 - 46
+            63,                                         // Slash at decimal 47
+            52,53,54,55,56,57,58,59,60,61,              // Numbers zero through nine
+            -9,-9,-9,                                   // Decimal 58 - 60
+            -1,                                         // Equals sign at decimal 61
+            -9,-9,-9,                                      // Decimal 62 - 64
+            0,1,2,3,4,5,6,7,8,9,10,11,12,13,            // Letters 'A' through 'N'
+            14,15,16,17,18,19,20,21,22,23,24,25,        // Letters 'O' through 'Z'
+            -9,-9,-9,-9,-9,-9,                          // Decimal 91 - 96
+            26,27,28,29,30,31,32,33,34,35,36,37,38,     // Letters 'a' through 'm'
+            39,40,41,42,43,44,45,46,47,48,49,50,51,     // Letters 'n' through 'z'
+            -9,-9,-9,-9,-9                              // Decimal 123 - 127
+            ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,       // Decimal 128 - 139
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255
+    };
+	
+	
+/* ********  U R L   S A F E   B A S E 6 4   A L P H A B E T  ******** */
+
+    /**
+     * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: 
+     * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+     * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash."
+     */
+    private final static byte[] _URL_SAFE_ALPHABET = {
+            (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+            (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+            (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+            (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+            (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_'
+    };
+
+    /**
+     * Used in decoding URL- and Filename-safe dialects of Base64.
+     */
+    private final static byte[] _URL_SAFE_DECODABET = {
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
+            -5,-5,                                      // Whitespace: Tab and Linefeed
+            -9,-9,                                      // Decimal 11 - 12
+            -5,                                         // Whitespace: Carriage Return
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
+            -9,-9,-9,-9,-9,                             // Decimal 27 - 31
+            -5,                                         // Whitespace: Space
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
+            -9,                                         // Plus sign at decimal 43
+            -9,                                         // Decimal 44
+            62,                                         // Minus sign at decimal 45
+            -9,                                         // Decimal 46
+            -9,                                         // Slash at decimal 47
+            52,53,54,55,56,57,58,59,60,61,              // Numbers zero through nine
+            -9,-9,-9,                                   // Decimal 58 - 60
+            -1,                                         // Equals sign at decimal 61
+            -9,-9,-9,                                   // Decimal 62 - 64
+            0,1,2,3,4,5,6,7,8,9,10,11,12,13,            // Letters 'A' through 'N'
+            14,15,16,17,18,19,20,21,22,23,24,25,        // Letters 'O' through 'Z'
+            -9,-9,-9,-9,                                // Decimal 91 - 94
+            63,                                         // Underscore at decimal 95
+            -9,                                         // Decimal 96
+            26,27,28,29,30,31,32,33,34,35,36,37,38,     // Letters 'a' through 'm'
+            39,40,41,42,43,44,45,46,47,48,49,50,51,     // Letters 'n' through 'z'
+            -9,-9,-9,-9,-9                              // Decimal 123 - 127
+            ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 128 - 139
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255
+    };
+
+
+
+/* ********  O R D E R E D   B A S E 6 4   A L P H A B E T  ******** */
+
+    /**
+     * I don't get the point of this technique, but someone requested it,
+     * and it is described here:
+     * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+     */
+    private final static byte[] _ORDERED_ALPHABET = {
+            (byte)'-',
+            (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4',
+            (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9',
+            (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+            (byte)'_',
+            (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+            (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z'
+    };
+
+    /**
+     * Used in decoding the "ordered" dialect of Base64.
+     */
+    private final static byte[] _ORDERED_DECODABET = {
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
+            -5,-5,                                      // Whitespace: Tab and Linefeed
+            -9,-9,                                      // Decimal 11 - 12
+            -5,                                         // Whitespace: Carriage Return
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
+            -9,-9,-9,-9,-9,                             // Decimal 27 - 31
+            -5,                                         // Whitespace: Space
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
+            -9,                                         // Plus sign at decimal 43
+            -9,                                         // Decimal 44
+            0,                                          // Minus sign at decimal 45
+            -9,                                         // Decimal 46
+            -9,                                         // Slash at decimal 47
+            1,2,3,4,5,6,7,8,9,10,                       // Numbers zero through nine
+            -9,-9,-9,                                   // Decimal 58 - 60
+            -1,                                         // Equals sign at decimal 61
+            -9,-9,-9,                                   // Decimal 62 - 64
+            11,12,13,14,15,16,17,18,19,20,21,22,23,     // Letters 'A' through 'M'
+            24,25,26,27,28,29,30,31,32,33,34,35,36,     // Letters 'N' through 'Z'
+            -9,-9,-9,-9,                                // Decimal 91 - 94
+            37,                                         // Underscore at decimal 95
+            -9,                                         // Decimal 96
+            38,39,40,41,42,43,44,45,46,47,48,49,50,     // Letters 'a' through 'm'
+            51,52,53,54,55,56,57,58,59,60,61,62,63,     // Letters 'n' through 'z'
+            -9,-9,-9,-9,-9                                 // Decimal 123 - 127
+            ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 128 - 139
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+            -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255
+    };
+
+	
+/* ********  D E T E R M I N E   W H I C H   A L H A B E T  ******** */
+
+
+    /**
+     * Returns one of the _SOMETHING_ALPHABET byte arrays depending on
+     * the options specified.
+     * It's possible, though silly, to specify ORDERED <b>and</b> URLSAFE
+     * in which case one of them will be picked, though there is
+     * no guarantee as to which one will be picked.
+     */
+    private final static byte[] getAlphabet( int options ) {
+        if ((options & URL_SAFE) == URL_SAFE) {
+            return _URL_SAFE_ALPHABET;
+        } else if ((options & ORDERED) == ORDERED) {
+            return _ORDERED_ALPHABET;
+        } else {
+            return _STANDARD_ALPHABET;
+        }
+    }	// end getAlphabet
+
+
+    /**
+     * Returns one of the _SOMETHING_DECODABET byte arrays depending on
+     * the options specified.
+     * It's possible, though silly, to specify ORDERED and URL_SAFE
+     * in which case one of them will be picked, though there is
+     * no guarantee as to which one will be picked.
+     */
+    private final static byte[] getDecodabet( int options ) {
+        if( (options & URL_SAFE) == URL_SAFE) {
+            return _URL_SAFE_DECODABET;
+        } else if ((options & ORDERED) == ORDERED) {
+            return _ORDERED_DECODABET;
+        } else {
+            return _STANDARD_DECODABET;
+        }
+    }	// end getAlphabet
+
+
+
+    /** Defeats instantiation. */
+    private Base64(){}
+    
+
+    
+    
+/* ********  E N C O D I N G   M E T H O D S  ******** */
+
+
+    /**
+     * Encodes up to the first three bytes of array <var>threeBytes</var>
+     * and returns a four-byte array in Base64 notation.
+     * The actual number of significant bytes in your array is
+     * given by <var>numSigBytes</var>.
+     * The array <var>threeBytes</var> needs only be as big as
+     * <var>numSigBytes</var>.
+     * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+     *
+     * @param b4 A reusable byte array to reduce array instantiation
+     * @param threeBytes the array to convert
+     * @param numSigBytes the number of significant bytes in your array
+     * @return four byte array in Base64 notation.
+     * @since 1.5.1
+     */
+    private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) {
+        encode3to4( threeBytes, 0, numSigBytes, b4, 0, options );
+        return b4;
+    }   // end encode3to4
+
+
+    /**
+     * <p>Encodes up to three bytes of the array <var>source</var>
+     * and writes the resulting four Base64 bytes to <var>destination</var>.
+     * The source and destination arrays can be manipulated
+     * anywhere along their length by specifying 
+     * <var>srcOffset</var> and <var>destOffset</var>.
+     * This method does not check to make sure your arrays
+     * are large enough to accomodate <var>srcOffset</var> + 3 for
+     * the <var>source</var> array or <var>destOffset</var> + 4 for
+     * the <var>destination</var> array.
+     * The actual number of significant bytes in your array is
+     * given by <var>numSigBytes</var>.</p>
+     * <p>This is the lowest level of the encoding methods with
+     * all possible parameters.</p>
+     *
+     * @param source the array to convert
+     * @param srcOffset the index where conversion begins
+     * @param numSigBytes the number of significant bytes in your array
+     * @param destination the array to hold the conversion
+     * @param destOffset the index where output will be put
+     * @return the <var>destination</var> array
+     * @since 1.3
+     */
+    private static byte[] encode3to4(
+            byte[] source, int srcOffset, int numSigBytes,
+            byte[] destination, int destOffset, int options ) {
+
+        byte[] ALPHABET = getAlphabet( options );
+
+        //           1         2         3  
+        // 01234567890123456789012345678901 Bit position
+        // --------000000001111111122222222 Array position from threeBytes
+        // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
+        //          >>18  >>12  >> 6  >> 0  Right shift necessary
+        //                0x3f  0x3f  0x3f  Additional AND
+
+        // Create buffer with zero-padding if there are only one or two
+        // significant bytes passed in the array.
+        // We have to shift left 24 in order to flush out the 1's that appear
+        // when Java treats a value as negative that is cast from a byte to an int.
+        int inBuff =   ( numSigBytes > 0 ? ((source[ srcOffset     ] << 24) >>>  8) : 0 )
+                | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
+                | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
+
+        switch( numSigBytes )
+        {
+            case 3:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
+                destination[ destOffset + 3 ] = ALPHABET[ (inBuff       ) & 0x3f ];
+                return destination;
+
+            case 2:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
+                destination[ destOffset + 3 ] = EQUALS_SIGN;
+                return destination;
+
+            case 1:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = EQUALS_SIGN;
+                destination[ destOffset + 3 ] = EQUALS_SIGN;
+                return destination;
+
+            default:
+                return destination;
+        }   // end switch
+    }   // end encode3to4
+
+
+
+    /**
+     * Performs Base64 encoding on the <code>raw</code> ByteBuffer,
+     * writing it to the <code>encoded</code> ByteBuffer.
+     * This is an experimental feature. Currently it does not
+     * pass along any options (such as {@link #DO_BREAK_LINES}
+     * or {@link #GZIP}.
+     *
+     * @param raw input buffer
+     * @param encoded output buffer
+     * @since 2.3
+     */
+    public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){
+        byte[] raw3 = new byte[3];
+        byte[] enc4 = new byte[4];
+
+        while( raw.hasRemaining() ){
+            int rem = Math.min(3,raw.remaining());
+            raw.get(raw3,0,rem);
+            Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
+            encoded.put(enc4);
+        }   // end input remaining
+    }
+
+
+    /**
+     * Performs Base64 encoding on the <code>raw</code> ByteBuffer,
+     * writing it to the <code>encoded</code> CharBuffer.
+     * This is an experimental feature. Currently it does not
+     * pass along any options (such as {@link #DO_BREAK_LINES}
+     * or {@link #GZIP}.
+     *
+     * @param raw input buffer
+     * @param encoded output buffer
+     * @since 2.3
+     */
+    public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){
+        byte[] raw3 = new byte[3];
+        byte[] enc4 = new byte[4];
+
+        while( raw.hasRemaining() ){
+            int rem = Math.min(3,raw.remaining());
+            raw.get(raw3,0,rem);
+            Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
+            for( int i = 0; i < 4; i++ ){
+                encoded.put( (char)(enc4[i] & 0xFF) );
+            }
+        }   // end input remaining
+    }
+
+
+
+
+    /**
+     * Serializes an object and returns the Base64-encoded
+     * version of that serialized object.  
+     *
+     * <p>As of v 2.3, if the object
+     * cannot be serialized or there is another error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned a null value, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * The object is not GZip-compressed before being encoded.
+     *
+     * @param serializableObject The object to encode
+     * @return The Base64-encoded object
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if serializedObject is null
+     * @since 1.4
+     */
+    public static String encodeObject( java.io.Serializable serializableObject )
+            throws java.io.IOException {
+        return encodeObject( serializableObject, NO_OPTIONS );
+    }   // end encodeObject
+
+
+
+    /**
+     * Serializes an object and returns the Base64-encoded
+     * version of that serialized object.
+     *
+     * <p>As of v 2.3, if the object
+     * cannot be serialized or there is another error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned a null value, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * The object is not GZip-compressed before being encoded.
+     * <p>
+     * Example options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     * </pre>
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+     *
+     * @param serializableObject The object to encode
+     * @param options Specified options
+     * @return The Base64-encoded object
+     * @see Base64#GZIP
+     * @see Base64#DO_BREAK_LINES
+     * @throws java.io.IOException if there is an error
+     * @since 2.0
+     */
+    public static String encodeObject( java.io.Serializable serializableObject, int options )
+            throws java.io.IOException {
+
+        if( serializableObject == null ){
+            throw new NullPointerException( "Cannot serialize a null object." );
+        }   // end if: null
+
+        // Streams
+        java.io.ByteArrayOutputStream  baos  = null;
+        java.io.OutputStream           b64os = null;
+        java.util.zip.GZIPOutputStream gzos  = null;
+        java.io.ObjectOutputStream     oos   = null;
+
+
+        try {
+            // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+            baos  = new java.io.ByteArrayOutputStream();
+            b64os = new Base64.OutputStream( baos, ENCODE | options );
+            if( (options & GZIP) != 0 ){
+                // Gzip
+                gzos = new java.util.zip.GZIPOutputStream(b64os);
+                oos = new java.io.ObjectOutputStream( gzos );
+            } else {
+                // Not gzipped
+                oos = new java.io.ObjectOutputStream( b64os );
+            }
+            oos.writeObject( serializableObject );
+        }   // end try
+        catch( java.io.IOException e ) {
+            // Catch it and then throw it immediately so that
+            // the finally{} block is called for cleanup.
+            throw e;
+        }   // end catch
+        finally {
+            try{ oos.close();   } catch( Exception e ){}
+            try{ gzos.close();  } catch( Exception e ){}
+            try{ b64os.close(); } catch( Exception e ){}
+            try{ baos.close();  } catch( Exception e ){}
+        }   // end finally
+
+        // Return value according to relevant encoding.
+        try {
+            return new String( baos.toByteArray(), PREFERRED_ENCODING );
+        }   // end try
+        catch (java.io.UnsupportedEncodingException uue){
+            // Fall back to some Java default
+            return new String( baos.toByteArray() );
+        }   // end catch
+
+    }   // end encode
+
+
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * Does not GZip-compress data.
+     *
+     * @param source The data to convert
+     * @return The data in Base64-encoded form
+     * @throws NullPointerException if source array is null
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source ) {
+        // Since we're not going to have the GZIP encoding turned on,
+        // we're not going to have an java.io.IOException thrown, so
+        // we should not force the user to have to catch it.
+        String encoded = null;
+        try {
+            encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
+        } catch (java.io.IOException ex) {
+            assert false : ex.getMessage();
+        }   // end catch
+        assert encoded != null;
+        return encoded;
+    }   // end encodeBytes
+
+
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Example options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+     *
+     *
+     * <p>As of v 2.3, if there is an error with the GZIP stream,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned a null value, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     *
+     * @param source The data to convert
+     * @param options Specified options
+     * @return The Base64-encoded data as a String
+     * @see Base64#GZIP
+     * @see Base64#DO_BREAK_LINES
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if source array is null
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source, int options ) throws java.io.IOException {
+        return encodeBytes( source, 0, source.length, options );
+    }   // end encodeBytes
+
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * Does not GZip-compress data.
+     *
+     * <p>As of v 2.3, if there is an error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned a null value, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     *
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @return The Base64-encoded data as a String
+     * @throws NullPointerException if source array is null
+     * @throws IllegalArgumentException if source array, offset, or length are invalid
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source, int off, int len ) {
+        // Since we're not going to have the GZIP encoding turned on,
+        // we're not going to have an java.io.IOException thrown, so
+        // we should not force the user to have to catch it.
+        String encoded = null;
+        try {
+            encoded = encodeBytes( source, off, len, NO_OPTIONS );
+        } catch (java.io.IOException ex) {
+            assert false : ex.getMessage();
+        }   // end catch
+        assert encoded != null;
+        return encoded;
+    }   // end encodeBytes
+
+
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Example options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+     *
+     *
+     * <p>As of v 2.3, if there is an error with the GZIP stream,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned a null value, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     *
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @param options Specified options
+     * @return The Base64-encoded data as a String
+     * @see Base64#GZIP
+     * @see Base64#DO_BREAK_LINES
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if source array is null
+     * @throws IllegalArgumentException if source array, offset, or length are invalid
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException {
+        byte[] encoded = encodeBytesToBytes( source, off, len, options );
+
+        // Return value according to relevant encoding.
+        try {
+            return new String( encoded, PREFERRED_ENCODING );
+        }   // end try
+        catch (java.io.UnsupportedEncodingException uue) {
+            return new String( encoded );
+        }   // end catch
+
+    }   // end encodeBytes
+
+
+
+
+    /**
+     * Similar to {@link #encodeBytes(byte[])} but returns
+     * a byte array instead of instantiating a String. This is more efficient
+     * if you're working with I/O streams and have large data sets to encode.
+     *
+     *
+     * @param source The data to convert
+     * @return The Base64-encoded data as a byte[] (of ASCII characters)
+     * @throws NullPointerException if source array is null
+     * @since 2.3.1
+     */
+    public static byte[] encodeBytesToBytes( byte[] source ) {
+        byte[] encoded = null;
+        try {
+            encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS );
+        } catch( java.io.IOException ex ) {
+            assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+        }
+        return encoded;
+    }
+
+
+    /**
+     * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns
+     * a byte array instead of instantiating a String. This is more efficient
+     * if you're working with I/O streams and have large data sets to encode.
+     *
+     *
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @param options Specified options
+     * @return The Base64-encoded data as a String
+     * @see Base64#GZIP
+     * @see Base64#DO_BREAK_LINES
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if source array is null
+     * @throws IllegalArgumentException if source array, offset, or length are invalid
+     * @since 2.3.1
+     */
+    public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException {
+
+        if( source == null ){
+            throw new NullPointerException( "Cannot serialize a null array." );
+        }   // end if: null
+
+        if( off < 0 ){
+            throw new IllegalArgumentException( "Cannot have negative offset: " + off );
+        }   // end if: off < 0
+
+        if( len < 0 ){
+            throw new IllegalArgumentException( "Cannot have length offset: " + len );
+        }   // end if: len < 0
+
+        if( off + len > source.length  ){
+            throw new IllegalArgumentException(
+                    String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length));
+        }   // end if: off < 0
+
+
+
+        // Compress?
+        if( (options & GZIP) != 0 ) {
+            java.io.ByteArrayOutputStream  baos  = null;
+            java.util.zip.GZIPOutputStream gzos  = null;
+            Base64.OutputStream            b64os = null;
+
+            try {
+                // GZip -> Base64 -> ByteArray
+                baos = new java.io.ByteArrayOutputStream();
+                b64os = new Base64.OutputStream( baos, ENCODE | options );
+                gzos  = new java.util.zip.GZIPOutputStream( b64os );
+
+                gzos.write( source, off, len );
+                gzos.close();
+            }   // end try
+            catch( java.io.IOException e ) {
+                // Catch it and then throw it immediately so that
+                // the finally{} block is called for cleanup.
+                throw e;
+            }   // end catch
+            finally {
+                try{ gzos.close();  } catch( Exception e ){}
+                try{ b64os.close(); } catch( Exception e ){}
+                try{ baos.close();  } catch( Exception e ){}
+            }   // end finally
+
+            return baos.toByteArray();
+        }   // end if: compress
+
+        // Else, don't compress. Better not to use streams at all then.
+        else {
+            boolean breakLines = (options & DO_BREAK_LINES) != 0;
+
+            //int    len43   = len * 4 / 3;
+            //byte[] outBuff = new byte[   ( len43 )                      // Main 4:3
+            //                           + ( (len % 3) > 0 ? 4 : 0 )      // Account for padding
+            //                           + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
+            // Try to determine more precisely how big the array needs to be.
+            // If we get it right, we don't have to do an array copy, and
+            // we save a bunch of memory.
+            int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding
+            if( breakLines ){
+                encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
+            }
+            byte[] outBuff = new byte[ encLen ];
+
+
+            int d = 0;
+            int e = 0;
+            int len2 = len - 2;
+            int lineLength = 0;
+            for( ; d < len2; d+=3, e+=4 ) {
+                encode3to4( source, d+off, 3, outBuff, e, options );
+
+                lineLength += 4;
+                if( breakLines && lineLength >= MAX_LINE_LENGTH )
+                {
+                    outBuff[e+4] = NEW_LINE;
+                    e++;
+                    lineLength = 0;
+                }   // end if: end of line
+            }   // en dfor: each piece of array
+
+            if( d < len ) {
+                encode3to4( source, d+off, len - d, outBuff, e, options );
+                e += 4;
+            }   // end if: some padding needed
+
+
+            // Only resize array if we didn't guess it right.
+            if( e <= outBuff.length - 1 ){
+                // If breaking lines and the last byte falls right at
+                // the line length (76 bytes per line), there will be
+                // one extra byte, and the array will need to be resized.
+                // Not too bad of an estimate on array size, I'd say.
+                byte[] finalOut = new byte[e];
+                System.arraycopy(outBuff,0, finalOut,0,e);
+                //System.err.println("Having to resize array from " + outBuff.length + " to " + e );
+                return finalOut;
+            } else {
+                //System.err.println("No need to resize array.");
+                return outBuff;
+            }
+
+        }   // end else: don't compress
+
+    }   // end encodeBytesToBytes
+    
+
+    
+    
+    
+/* ********  D E C O D I N G   M E T H O D S  ******** */
+
+
+    /**
+     * Decodes four bytes from array <var>source</var>
+     * and writes the resulting bytes (up to three of them)
+     * to <var>destination</var>.
+     * The source and destination arrays can be manipulated
+     * anywhere along their length by specifying 
+     * <var>srcOffset</var> and <var>destOffset</var>.
+     * This method does not check to make sure your arrays
+     * are large enough to accomodate <var>srcOffset</var> + 4 for
+     * the <var>source</var> array or <var>destOffset</var> + 3 for
+     * the <var>destination</var> array.
+     * This method returns the actual number of bytes that 
+     * were converted from the Base64 encoding.
+     * <p>This is the lowest level of the decoding methods with
+     * all possible parameters.</p>
+     *
+     *
+     * @param source the array to convert
+     * @param srcOffset the index where conversion begins
+     * @param destination the array to hold the conversion
+     * @param destOffset the index where output will be put
+     * @param options alphabet type is pulled from this (standard, url-safe, ordered)
+     * @return the number of decoded bytes converted
+     * @throws NullPointerException if source or destination arrays are null
+     * @throws IllegalArgumentException if srcOffset or destOffset are invalid
+     *         or there is not enough room in the array.
+     * @since 1.3
+     */
+    private static int decode4to3(
+            byte[] source, int srcOffset,
+            byte[] destination, int destOffset, int options ) {
+
+        // Lots of error checking and exception throwing
+        if( source == null ){
+            throw new NullPointerException( "Source array was null." );
+        }   // end if
+        if( destination == null ){
+            throw new NullPointerException( "Destination array was null." );
+        }   // end if
+        if( srcOffset < 0 || srcOffset + 3 >= source.length ){
+            throw new IllegalArgumentException( String.format(
+                    "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) );
+        }   // end if
+        if( destOffset < 0 || destOffset +2 >= destination.length ){
+            throw new IllegalArgumentException( String.format(
+                    "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) );
+        }   // end if
+
+
+        byte[] DECODABET = getDecodabet( options );
+
+        // Example: Dk==
+        if( source[ srcOffset + 2] == EQUALS_SIGN ) {
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] & 0xFF ) << 18 )
+                    | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
+
+            destination[ destOffset ] = (byte)( outBuff >>> 16 );
+            return 1;
+        }
+
+        // Example: DkL=
+        else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) {
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+            //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
+                    | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+                    | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6 );
+
+            destination[ destOffset     ] = (byte)( outBuff >>> 16 );
+            destination[ destOffset + 1 ] = (byte)( outBuff >>>  8 );
+            return 2;
+        }
+
+        // Example: DkLE
+        else {
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+            //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+            //              | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
+                    | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+                    | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6)
+                    | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF )      );
+
+
+            destination[ destOffset     ] = (byte)( outBuff >> 16 );
+            destination[ destOffset + 1 ] = (byte)( outBuff >>  8 );
+            destination[ destOffset + 2 ] = (byte)( outBuff       );
+
+            return 3;
+        }
+    }   // end decodeToBytes
+
+
+
+
+
+    /**
+     * Low-level access to decoding ASCII characters in
+     * the form of a byte array. <strong>Ignores GUNZIP option, if
+     * it's set.</strong> This is not generally a recommended method,
+     * although it is used internally as part of the decoding process.
+     * Special case: if len = 0, an empty array is returned. Still,
+     * if you need more speed and reduced memory footprint (and aren't
+     * gzipping), consider this method.
+     *
+     * @param source The Base64 encoded data
+     * @return decoded data
+     * @since 2.3.1
+     */
+    public static byte[] decode( byte[] source )
+            throws java.io.IOException {
+        byte[] decoded = null;
+//        try {
+        decoded = decode( source, 0, source.length, Base64.NO_OPTIONS );
+//        } catch( java.io.IOException ex ) {
+//            assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+//        }
+        return decoded;
+    }
+
+
+
+    /**
+     * Low-level access to decoding ASCII characters in
+     * the form of a byte array. <strong>Ignores GUNZIP option, if
+     * it's set.</strong> This is not generally a recommended method,
+     * although it is used internally as part of the decoding process.
+     * Special case: if len = 0, an empty array is returned. Still,
+     * if you need more speed and reduced memory footprint (and aren't
+     * gzipping), consider this method.
+     *
+     * @param source The Base64 encoded data
+     * @param off    The offset of where to begin decoding
+     * @param len    The length of characters to decode
+     * @param options Can specify options such as alphabet type to use
+     * @return decoded data
+     * @throws java.io.IOException If bogus characters exist in source data
+     * @since 1.3
+     */
+    public static byte[] decode( byte[] source, int off, int len, int options )
+            throws java.io.IOException {
+
+        // Lots of error checking and exception throwing
+        if( source == null ){
+            throw new NullPointerException( "Cannot decode null source array." );
+        }   // end if
+        if( off < 0 || off + len > source.length ){
+            throw new IllegalArgumentException( String.format(
+                    "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) );
+        }   // end if
+
+        if( len == 0 ){
+            return new byte[0];
+        }else if( len < 4 ){
+            throw new IllegalArgumentException(
+                    "Base64-encoded string must have at least four characters, but length specified was " + len );
+        }   // end if
+
+        byte[] DECODABET = getDecodabet( options );
+
+        int    len34   = len * 3 / 4;       // Estimate on array size
+        byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
+        int    outBuffPosn = 0;             // Keep track of where we're writing
+
+        byte[] b4        = new byte[4];     // Four byte buffer from source, eliminating white space
+        int    b4Posn    = 0;               // Keep track of four byte input buffer
+        int    i         = 0;               // Source array counter
+        byte   sbiDecode = 0;               // Special value from DECODABET
+
+        for( i = off; i < off+len; i++ ) {  // Loop through source
+
+            sbiDecode = DECODABET[ source[i]&0xFF ];
+
+            // White space, Equals sign, or legit Base64 character
+            // Note the values such as -5 and -9 in the
+            // DECODABETs at the top of the file.
+            if( sbiDecode >= WHITE_SPACE_ENC )  {
+                if( sbiDecode >= EQUALS_SIGN_ENC ) {
+                    b4[ b4Posn++ ] = source[i];         // Save non-whitespace
+                    if( b4Posn > 3 ) {                  // Time to decode?
+                        outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options );
+                        b4Posn = 0;
+
+                        // If that was the equals sign, break out of 'for' loop
+                        if( source[i] == EQUALS_SIGN ) {
+                            break;
+                        }   // end if: equals sign
+                    }   // end if: quartet built
+                }   // end if: equals sign or better
+            }   // end if: white space, equals sign or better
+            else {
+                // There's a bad input character in the Base64 stream.
+                throw new java.io.IOException( String.format(
+                        "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) );
+            }   // end else: 
+        }   // each input character
+
+        byte[] out = new byte[ outBuffPosn ];
+        System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
+        return out;
+    }   // end decode
+
+
+
+
+    /**
+     * Decodes data from Base64 notation, automatically
+     * detecting gzip-compressed data and decompressing it.
+     *
+     * @param s the string to decode
+     * @return the decoded data
+     * @throws java.io.IOException If there is a problem
+     * @since 1.4
+     */
+    public static byte[] decode( String s ) throws java.io.IOException {
+        return decode( s, NO_OPTIONS );
+    }
+
+
+
+    /**
+     * Decodes data from Base64 notation, automatically
+     * detecting gzip-compressed data and decompressing it.
+     *
+     * @param s the string to decode
+     * @param options encode options such as URL_SAFE
+     * @return the decoded data
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if <tt>s</tt> is null
+     * @since 1.4
+     */
+    public static byte[] decode( String s, int options ) throws java.io.IOException {
+
+        if( s == null ){
+            throw new NullPointerException( "Input string was null." );
+        }   // end if
+
+        byte[] bytes;
+        try {
+            bytes = s.getBytes( PREFERRED_ENCODING );
+        }   // end try
+        catch( java.io.UnsupportedEncodingException uee ) {
+            bytes = s.getBytes();
+        }   // end catch
+        //</change>
+
+        // Decode
+        bytes = decode( bytes, 0, bytes.length, options );
+
+        // Check to see if it's gzip-compressed
+        // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+        boolean dontGunzip = (options & DONT_GUNZIP) != 0;
+        if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) {
+
+            int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+            if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head )  {
+                java.io.ByteArrayInputStream  bais = null;
+                java.util.zip.GZIPInputStream gzis = null;
+                java.io.ByteArrayOutputStream baos = null;
+                byte[] buffer = new byte[2048];
+                int    length = 0;
+
+                try {
+                    baos = new java.io.ByteArrayOutputStream();
+                    bais = new java.io.ByteArrayInputStream( bytes );
+                    gzis = new java.util.zip.GZIPInputStream( bais );
+
+                    while( ( length = gzis.read( buffer ) ) >= 0 ) {
+                        baos.write(buffer,0,length);
+                    }   // end while: reading input
+
+                    // No error? Get new bytes.
+                    bytes = baos.toByteArray();
+
+                }   // end try
+                catch( java.io.IOException e ) {
+                    e.printStackTrace();
+                    // Just return originally-decoded bytes
+                }   // end catch
+                finally {
+                    try{ baos.close(); } catch( Exception e ){}
+                    try{ gzis.close(); } catch( Exception e ){}
+                    try{ bais.close(); } catch( Exception e ){}
+                }   // end finally
+
+            }   // end if: gzipped
+        }   // end if: bytes.length >= 2
+
+        return bytes;
+    }   // end decode
+
+
+
+    /**
+     * Attempts to decode Base64 data and deserialize a Java
+     * Object within. Returns <tt>null</tt> if there was an error.
+     *
+     * @param encodedObject The Base64 data to decode
+     * @return The decoded and deserialized object
+     * @throws NullPointerException if encodedObject is null
+     * @throws java.io.IOException if there is a general error
+     * @throws ClassNotFoundException if the decoded object is of a
+     *         class that cannot be found by the JVM
+     * @since 1.5
+     */
+    public static Object decodeToObject( String encodedObject )
+            throws java.io.IOException, java.lang.ClassNotFoundException {
+        return decodeToObject(encodedObject,NO_OPTIONS,null);
+    }
+
+
+    /**
+     * Attempts to decode Base64 data and deserialize a Java
+     * Object within. Returns <tt>null</tt> if there was an error.
+     * If <tt>loader</tt> is not null, it will be the class loader
+     * used when deserializing.
+     *
+     * @param encodedObject The Base64 data to decode
+     * @param options Various parameters related to decoding
+     * @param loader Optional class loader to use in deserializing classes.
+     * @return The decoded and deserialized object
+     * @throws NullPointerException if encodedObject is null
+     * @throws java.io.IOException if there is a general error
+     * @throws ClassNotFoundException if the decoded object is of a 
+     *         class that cannot be found by the JVM
+     * @since 2.3.4
+     */
+    public static Object decodeToObject(
+            String encodedObject, int options, final ClassLoader loader )
+            throws java.io.IOException, java.lang.ClassNotFoundException {
+
+        // Decode and gunzip if necessary
+        byte[] objBytes = decode( encodedObject, options );
+
+        java.io.ByteArrayInputStream  bais = null;
+        java.io.ObjectInputStream     ois  = null;
+        Object obj = null;
+
+        try {
+            bais = new java.io.ByteArrayInputStream( objBytes );
+
+            // If no custom class loader is provided, use Java's builtin OIS.
+            if( loader == null ){
+                ois  = new java.io.ObjectInputStream( bais );
+            }   // end if: no loader provided
+
+            // Else make a customized object input stream that uses
+            // the provided class loader.
+            else {
+                ois = new java.io.ObjectInputStream(bais){
+                    @Override
+                    public Class<?> resolveClass(java.io.ObjectStreamClass streamClass)
+                            throws java.io.IOException, ClassNotFoundException {
+                        Class c = Class.forName(streamClass.getName(), false, loader);
+                        if( c == null ){
+                            return super.resolveClass(streamClass);
+                        } else {
+                            return c;   // Class loader knows of this class.
+                        }   // end else: not null
+                    }   // end resolveClass
+                };  // end ois
+            }   // end else: no custom class loader
+
+            obj = ois.readObject();
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e;    // Catch and throw in order to execute finally{}
+        }   // end catch
+        catch( java.lang.ClassNotFoundException e ) {
+            throw e;    // Catch and throw in order to execute finally{}
+        }   // end catch
+        finally {
+            try{ bais.close(); } catch( Exception e ){}
+            try{ ois.close();  } catch( Exception e ){}
+        }   // end finally
+
+        return obj;
+    }   // end decodeObject
+
+
+
+    /**
+     * Convenience method for encoding data to a file.
+     *
+     * <p>As of v 2.3, if there is a error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned false, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * @param dataToEncode byte array of data to encode in base64 form
+     * @param filename Filename for saving encoded data
+     * @throws java.io.IOException if there is an error
+     * @throws NullPointerException if dataToEncode is null
+     * @since 2.1
+     */
+    public static void encodeToFile( byte[] dataToEncode, String filename )
+            throws java.io.IOException {
+
+        if( dataToEncode == null ){
+            throw new NullPointerException( "Data to encode was null." );
+        }   // end iff
+
+        Base64.OutputStream bos = null;
+        try {
+            bos = new Base64.OutputStream(
+                    new java.io.FileOutputStream( filename ), Base64.ENCODE );
+            bos.write( dataToEncode );
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and throw to execute finally{} block
+        }   // end catch: java.io.IOException
+        finally {
+            try{ bos.close(); } catch( Exception e ){}
+        }   // end finally
+
+    }   // end encodeToFile
+
+
+    /**
+     * Convenience method for decoding data to a file.
+     *
+     * <p>As of v 2.3, if there is a error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned false, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * @param dataToDecode Base64-encoded data as a string
+     * @param filename Filename for saving decoded data
+     * @throws java.io.IOException if there is an error
+     * @since 2.1
+     */
+    public static void decodeToFile( String dataToDecode, String filename )
+            throws java.io.IOException {
+
+        Base64.OutputStream bos = null;
+        try{
+            bos = new Base64.OutputStream(
+                    new java.io.FileOutputStream( filename ), Base64.DECODE );
+            bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and throw to execute finally{} block
+        }   // end catch: java.io.IOException
+        finally {
+            try{ bos.close(); } catch( Exception e ){}
+        }   // end finally
+
+    }   // end decodeToFile
+
+
+
+
+    /**
+     * Convenience method for reading a base64-encoded
+     * file and decoding it.
+     *
+     * <p>As of v 2.3, if there is a error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned false, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * @param filename Filename for reading encoded data
+     * @return decoded byte array
+     * @throws java.io.IOException if there is an error
+     * @since 2.1
+     */
+    public static byte[] decodeFromFile( String filename )
+            throws java.io.IOException {
+
+        byte[] decodedData = null;
+        Base64.InputStream bis = null;
+        try
+        {
+            // Set up some useful variables
+            java.io.File file = new java.io.File( filename );
+            byte[] buffer = null;
+            int length   = 0;
+            int numBytes = 0;
+
+            // Check for size of file
+            if( file.length() > Integer.MAX_VALUE )
+            {
+                throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." );
+            }   // end if: file too big for int index
+            buffer = new byte[ (int)file.length() ];
+
+            // Open a stream
+            bis = new Base64.InputStream(
+                    new java.io.BufferedInputStream(
+                            new java.io.FileInputStream( file ) ), Base64.DECODE );
+
+            // Read until done
+            while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) {
+                length += numBytes;
+            }   // end while
+
+            // Save in a variable to return
+            decodedData = new byte[ length ];
+            System.arraycopy( buffer, 0, decodedData, 0, length );
+
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and release to execute finally{}
+        }   // end catch: java.io.IOException
+        finally {
+            try{ bis.close(); } catch( Exception e) {}
+        }   // end finally
+
+        return decodedData;
+    }   // end decodeFromFile
+
+
+
+    /**
+     * Convenience method for reading a binary file
+     * and base64-encoding it.
+     *
+     * <p>As of v 2.3, if there is a error,
+     * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+     * In earlier versions, it just returned false, but
+     * in retrospect that's a pretty poor way to handle it.</p>
+     *
+     * @param filename Filename for reading binary data
+     * @return base64-encoded string
+     * @throws java.io.IOException if there is an error
+     * @since 2.1
+     */
+    public static String encodeFromFile( String filename )
+            throws java.io.IOException {
+
+        String encodedData = null;
+        Base64.InputStream bis = null;
+        try
+        {
+            // Set up some useful variables
+            java.io.File file = new java.io.File( filename );
+            byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5)
+            int length   = 0;
+            int numBytes = 0;
+
+            // Open a stream
+            bis = new Base64.InputStream(
+                    new java.io.BufferedInputStream(
+                            new java.io.FileInputStream( file ) ), Base64.ENCODE );
+
+            // Read until done
+            while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) {
+                length += numBytes;
+            }   // end while
+
+            // Save in a variable to return
+            encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
+
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and release to execute finally{}
+        }   // end catch: java.io.IOException
+        finally {
+            try{ bis.close(); } catch( Exception e) {}
+        }   // end finally
+
+        return encodedData;
+    }   // end encodeFromFile
+
+    /**
+     * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
+     *
+     * @param infile Input file
+     * @param outfile Output file
+     * @throws java.io.IOException if there is an error
+     * @since 2.2
+     */
+    public static void encodeFileToFile( String infile, String outfile )
+            throws java.io.IOException {
+
+        String encoded = Base64.encodeFromFile( infile );
+        java.io.OutputStream out = null;
+        try{
+            out = new java.io.BufferedOutputStream(
+                    new java.io.FileOutputStream( outfile ) );
+            out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output.
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and release to execute finally{}
+        }   // end catch
+        finally {
+            try { out.close(); }
+            catch( Exception ex ){}
+        }   // end finally    
+    }   // end encodeFileToFile
+
+
+    /**
+     * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
+     *
+     * @param infile Input file
+     * @param outfile Output file
+     * @throws java.io.IOException if there is an error
+     * @since 2.2
+     */
+    public static void decodeFileToFile( String infile, String outfile )
+            throws java.io.IOException {
+
+        byte[] decoded = Base64.decodeFromFile( infile );
+        java.io.OutputStream out = null;
+        try{
+            out = new java.io.BufferedOutputStream(
+                    new java.io.FileOutputStream( outfile ) );
+            out.write( decoded );
+        }   // end try
+        catch( java.io.IOException e ) {
+            throw e; // Catch and release to execute finally{}
+        }   // end catch
+        finally {
+            try { out.close(); }
+            catch( Exception ex ){}
+        }   // end finally    
+    }   // end decodeFileToFile
+    
+    
+    /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
+
+
+
+    /**
+     * A {@link Base64.InputStream} will read data from another
+     * <tt>java.io.InputStream</tt>, given in the constructor,
+     * and encode/decode to/from Base64 notation on the fly.
+     *
+     * @see Base64
+     * @since 1.3
+     */
+    public static class InputStream extends java.io.FilterInputStream {
+
+        private boolean encode;         // Encoding or decoding
+        private int     position;       // Current position in the buffer
+        private byte[]  buffer;         // Small buffer holding converted data
+        private int     bufferLength;   // Length of buffer (3 or 4)
+        private int     numSigBytes;    // Number of meaningful bytes in the buffer
+        private int     lineLength;
+        private boolean breakLines;     // Break lines at less than 80 characters
+        private int     options;        // Record options used to create the stream.
+        private byte[]  decodabet;      // Local copies to avoid extra method calls
+
+
+        /**
+         * Constructs a {@link Base64.InputStream} in DECODE mode.
+         *
+         * @param in the <tt>java.io.InputStream</tt> from which to read data.
+         * @since 1.3
+         */
+        public InputStream( java.io.InputStream in ) {
+            this( in, DECODE );
+        }   // end constructor
+
+
+        /**
+         * Constructs a {@link Base64.InputStream} in
+         * either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:<pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: break lines at 76 characters
+         *     (only meaningful when encoding)</i>
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
+         *
+         *
+         * @param in the <tt>java.io.InputStream</tt> from which to read data.
+         * @param options Specified options
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DO_BREAK_LINES
+         * @since 2.0
+         */
+        public InputStream( java.io.InputStream in, int options ) {
+
+            super( in );
+            this.options      = options; // Record for later
+            this.breakLines   = (options & DO_BREAK_LINES) > 0;
+            this.encode       = (options & ENCODE) > 0;
+            this.bufferLength = encode ? 4 : 3;
+            this.buffer       = new byte[ bufferLength ];
+            this.position     = -1;
+            this.lineLength   = 0;
+            this.decodabet    = getDecodabet(options);
+        }   // end constructor
+
+        /**
+         * Reads enough of the input stream to convert
+         * to/from Base64 and returns the next byte.
+         *
+         * @return next byte
+         * @since 1.3
+         */
+        @Override
+        public int read() throws java.io.IOException  {
+
+            // Do we need to get data?
+            if( position < 0 ) {
+                if( encode ) {
+                    byte[] b3 = new byte[3];
+                    int numBinaryBytes = 0;
+                    for( int i = 0; i < 3; i++ ) {
+                        int b = in.read();
+
+                        // If end of stream, b is -1.
+                        if( b >= 0 ) {
+                            b3[i] = (byte)b;
+                            numBinaryBytes++;
+                        } else {
+                            break; // out of for loop
+                        }   // end else: end of stream
+
+                    }   // end for: each needed input byte
+
+                    if( numBinaryBytes > 0 ) {
+                        encode3to4( b3, 0, numBinaryBytes, buffer, 0, options );
+                        position = 0;
+                        numSigBytes = 4;
+                    }   // end if: got data
+                    else {
+                        return -1;  // Must be end of stream
+                    }   // end else
+                }   // end if: encoding
+
+                // Else decoding
+                else {
+                    byte[] b4 = new byte[4];
+                    int i = 0;
+                    for( i = 0; i < 4; i++ ) {
+                        // Read four "meaningful" bytes:
+                        int b = 0;
+                        do{ b = in.read(); }
+                        while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC );
+
+                        if( b < 0 ) {
+                            break; // Reads a -1 if end of stream
+                        }   // end if: end of stream
+
+                        b4[i] = (byte)b;
+                    }   // end for: each needed input byte
+
+                    if( i == 4 ) {
+                        numSigBytes = decode4to3( b4, 0, buffer, 0, options );
+                        position = 0;
+                    }   // end if: got four characters
+                    else if( i == 0 ){
+                        return -1;
+                    }   // end else if: also padded correctly
+                    else {
+                        // Must have broken out from above.
+                        throw new java.io.IOException( "Improperly padded Base64 input." );
+                    }   // end 
+
+                }   // end else: decode
+            }   // end else: get data
+
+            // Got data?
+            if( position >= 0 ) {
+                // End of relevant data?
+                if( /*!encode &&*/ position >= numSigBytes ){
+                    return -1;
+                }   // end if: got data
+
+                if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) {
+                    lineLength = 0;
+                    return '\n';
+                }   // end if
+                else {
+                    lineLength++;   // This isn't important when decoding
+                    // but throwing an extra "if" seems
+                    // just as wasteful.
+
+                    int b = buffer[ position++ ];
+
+                    if( position >= bufferLength ) {
+                        position = -1;
+                    }   // end if: end
+
+                    return b & 0xFF; // This is how you "cast" a byte that's
+                    // intended to be unsigned.
+                }   // end else
+            }   // end if: position >= 0
+
+            // Else error
+            else {
+                throw new java.io.IOException( "Error in Base64 code reading stream." );
+            }   // end else
+        }   // end read
+
+
+        /**
+         * Calls {@link #read()} repeatedly until the end of stream
+         * is reached or <var>len</var> bytes are read.
+         * Returns number of bytes read into array or -1 if
+         * end of stream is encountered.
+         *
+         * @param dest array to hold values
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @return bytes read into array or -1 if end of stream is encountered.
+         * @since 1.3
+         */
+        @Override
+        public int read( byte[] dest, int off, int len )
+                throws java.io.IOException {
+            int i;
+            int b;
+            for( i = 0; i < len; i++ ) {
+                b = read();
+
+                if( b >= 0 ) {
+                    dest[off + i] = (byte) b;
+                }
+                else if( i == 0 ) {
+                    return -1;
+                }
+                else {
+                    break; // Out of 'for' loop
+                } // Out of 'for' loop
+            }   // end for: each byte read
+            return i;
+        }   // end read
+
+    }   // end inner class InputStream
+    
+    
+    
+    
+    
+    
+    /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
+
+
+
+    /**
+     * A {@link Base64.OutputStream} will write data to another
+     * <tt>java.io.OutputStream</tt>, given in the constructor,
+     * and encode/decode to/from Base64 notation on the fly.
+     *
+     * @see Base64
+     * @since 1.3
+     */
+    public static class OutputStream extends java.io.FilterOutputStream {
+
+        private boolean encode;
+        private int     position;
+        private byte[]  buffer;
+        private int     bufferLength;
+        private int     lineLength;
+        private boolean breakLines;
+        private byte[]  b4;         // Scratch used in a few places
+        private boolean suspendEncoding;
+        private int     options;    // Record for later
+        private byte[]  decodabet;  // Local copies to avoid extra method calls
+
+        /**
+         * Constructs a {@link Base64.OutputStream} in ENCODE mode.
+         *
+         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out ) {
+            this( out, ENCODE );
+        }   // end constructor
+
+
+        /**
+         * Constructs a {@link Base64.OutputStream} in
+         * either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:<pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)</i>
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
+         *
+         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+         * @param options Specified options.
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DO_BREAK_LINES
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out, int options ) {
+            super( out );
+            this.breakLines   = (options & DO_BREAK_LINES) != 0;
+            this.encode       = (options & ENCODE) != 0;
+            this.bufferLength = encode ? 3 : 4;
+            this.buffer       = new byte[ bufferLength ];
+            this.position     = 0;
+            this.lineLength   = 0;
+            this.suspendEncoding = false;
+            this.b4           = new byte[4];
+            this.options      = options;
+            this.decodabet    = getDecodabet(options);
+        }   // end constructor
+
+
+        /**
+         * Writes the byte to the output stream after
+         * converting to/from Base64 notation.
+         * When encoding, bytes are buffered three
+         * at a time before the output stream actually
+         * gets a write() call.
+         * When decoding, bytes are buffered four
+         * at a time.
+         *
+         * @param theByte the byte to write
+         * @since 1.3
+         */
+        @Override
+        public void write(int theByte)
+                throws java.io.IOException {
+            // Encoding suspended?
+            if( suspendEncoding ) {
+                this.out.write( theByte );
+                return;
+            }   // end if: supsended
+
+            // Encode?
+            if( encode ) {
+                buffer[ position++ ] = (byte)theByte;
+                if( position >= bufferLength ) { // Enough to encode.
+
+                    this.out.write( encode3to4( b4, buffer, bufferLength, options ) );
+
+                    lineLength += 4;
+                    if( breakLines && lineLength >= MAX_LINE_LENGTH ) {
+                        this.out.write( NEW_LINE );
+                        lineLength = 0;
+                    }   // end if: end of line
+
+                    position = 0;
+                }   // end if: enough to output
+            }   // end if: encoding
+
+            // Else, Decoding
+            else {
+                // Meaningful Base64 character?
+                if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) {
+                    buffer[ position++ ] = (byte)theByte;
+                    if( position >= bufferLength ) { // Enough to output.
+
+                        int len = Base64.decode4to3( buffer, 0, b4, 0, options );
+                        out.write( b4, 0, len );
+                        position = 0;
+                    }   // end if: enough to output
+                }   // end if: meaningful base64 character
+                else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) {
+                    throw new java.io.IOException( "Invalid character in Base64 data." );
+                }   // end else: not white space either
+            }   // end else: decoding
+        }   // end write
+
+
+
+        /**
+         * Calls {@link #write(int)} repeatedly until <var>len</var> 
+         * bytes are written.
+         *
+         * @param theBytes array from which to read bytes
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @since 1.3
+         */
+        @Override
+        public void write( byte[] theBytes, int off, int len )
+                throws java.io.IOException {
+            // Encoding suspended?
+            if( suspendEncoding ) {
+                this.out.write( theBytes, off, len );
+                return;
+            }   // end if: supsended
+
+            for( int i = 0; i < len; i++ ) {
+                write( theBytes[ off + i ] );
+            }   // end for: each byte written
+
+        }   // end write
+
+
+
+        /**
+         * Method added by PHIL. [Thanks, PHIL. -Rob]
+         * This pads the buffer without closing the stream.
+         * @throws java.io.IOException  if there's an error.
+         */
+        public void flushBase64() throws java.io.IOException  {
+            if( position > 0 ) {
+                if( encode ) {
+                    out.write( encode3to4( b4, buffer, position, options ) );
+                    position = 0;
+                }   // end if: encoding
+                else {
+                    throw new java.io.IOException( "Base64 input not properly padded." );
+                }   // end else: decoding
+            }   // end if: buffer partially full
+
+        }   // end flush
+
+
+        /**
+         * Flushes and closes (I think, in the superclass) the stream. 
+         *
+         * @since 1.3
+         */
+        @Override
+        public void close() throws java.io.IOException {
+            // 1. Ensure that pending characters are written
+            flushBase64();
+
+            // 2. Actually close the stream
+            // Base class both flushes and closes.
+            super.close();
+
+            buffer = null;
+            out    = null;
+        }   // end close
+
+
+
+        /**
+         * Suspends encoding of the stream.
+         * May be helpful if you need to embed a piece of
+         * base64-encoded data in a stream.
+         *
+         * @throws java.io.IOException  if there's an error flushing
+         * @since 1.5.1
+         */
+        public void suspendEncoding() throws java.io.IOException  {
+            flushBase64();
+            this.suspendEncoding = true;
+        }   // end suspendEncoding
+
+
+        /**
+         * Resumes encoding of the stream.
+         * May be helpful if you need to embed a piece of
+         * base64-encoded data in a stream.
+         *
+         * @since 1.5.1
+         */
+        public void resumeEncoding() {
+            this.suspendEncoding = false;
+        }   // end resumeEncoding
+
+
+
+    }   // end inner class OutputStream
+
+
+}   // end class Base64
diff --git a/core/src/main/java/org/keycloak/util/Base64Url.java b/core/src/main/java/org/keycloak/util/Base64Url.java
index a57ed1a..c5d99e8 100755
--- a/core/src/main/java/org/keycloak/util/Base64Url.java
+++ b/core/src/main/java/org/keycloak/util/Base64Url.java
@@ -1,7 +1,7 @@
 package org.keycloak.util;
 
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
diff --git a/core/src/main/java/org/keycloak/util/BasicAuthHelper.java b/core/src/main/java/org/keycloak/util/BasicAuthHelper.java
index d5f8918..65f8e88 100755
--- a/core/src/main/java/org/keycloak/util/BasicAuthHelper.java
+++ b/core/src/main/java/org/keycloak/util/BasicAuthHelper.java
@@ -1,6 +1,6 @@
 package org.keycloak.util;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
diff --git a/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java b/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java
index 2963623..e95beaa 100644
--- a/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java
+++ b/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java
@@ -12,7 +12,7 @@ import java.lang.reflect.Method;
 
 import javax.security.auth.kerberos.KerberosTicket;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
diff --git a/core/src/main/java/org/keycloak/util/PemUtils.java b/core/src/main/java/org/keycloak/util/PemUtils.java
index 90624aa..ea07e35 100755
--- a/core/src/main/java/org/keycloak/util/PemUtils.java
+++ b/core/src/main/java/org/keycloak/util/PemUtils.java
@@ -1,7 +1,7 @@
 package org.keycloak.util;
 
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index b89c6a6..32755c2 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -20,10 +20,6 @@
             <artifactId>keycloak-core</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>
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..1f02898 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
@@ -12,8 +12,8 @@
             <directory>${project.build.directory}/unpacked</directory>
             <includes>
                 <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..e58f2b6 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/build.xml
@@ -42,10 +42,6 @@
             <maven-resource group="org.keycloak" artifact="keycloak-core"/>
         </module-def>
 
-        <module-def name="net.iharder.base64">
-            <maven-resource group="net.iharder" artifact="base64"/>
-        </module-def>
-
         <module-def name="org.bouncycastle">
             <maven-resource group="org.bouncycastle" artifact="bcprov-jdk15on"/>
             <maven-resource group="org.bouncycastle" artifact="bcpkix-jdk15on"/>
@@ -53,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-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..4e23733 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>
@@ -46,10 +54,6 @@
             <artifactId>keycloak-servlet-oauth-client</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </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..bb8407f 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,7 +14,7 @@
         <module name="org.apache.httpcomponents"/>
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
-        <module name="net.iharder.base64"/>
+        <module name="org.keycloak.keycloak-adapter-spi"/>
     </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-core/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index 545f168..dab8040 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
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..0f2a75b 100755
--- a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
+++ b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
@@ -12,8 +12,8 @@
             <directory>${project.build.directory}/unpacked</directory>
             <includes>
                 <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/osgi/thirdparty/pom.xml b/distribution/adapters/osgi/thirdparty/pom.xml
index c4ddc7a..3389306 100755
--- a/distribution/adapters/osgi/thirdparty/pom.xml
+++ b/distribution/adapters/osgi/thirdparty/pom.xml
@@ -17,7 +17,6 @@
 
     <properties>
         <keycloak.osgi.export>
-            net.iharder;version="${base64.version}",
             org.apache.http.*;version=${apache.httpcomponents.version}
         </keycloak.osgi.export>
         <keycloak.osgi.import>
@@ -27,10 +26,6 @@
 
     <dependencies>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
         </dependency>
@@ -53,7 +48,7 @@
                 </executions>
                 <configuration>
                     <instructions>
-                        <Embed-Dependency>*;scope=compile|runtime;artifactId=!httpclient|httpcore|base64</Embed-Dependency>
+                        <Embed-Dependency>*;scope=compile|runtime;artifactId=!httpclient|httpcore</Embed-Dependency>
                         <Embed-Transitive>true</Embed-Transitive>
                         <Bundle-ClassPath>.</Bundle-ClassPath>
                         <Bundle-Name>${project.name}</Bundle-Name>
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
index da4e127..ad0cc64 100755
--- a/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
@@ -11,9 +11,9 @@
         <fileSet>
             <directory>${project.build.directory}/unpacked</directory>
             <includes>
-                <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..e442fef 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/build.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/build.xml
@@ -42,13 +42,14 @@
             <maven-resource group="org.keycloak" artifact="keycloak-core"/>
         </module-def>
 
-        <module-def name="net.iharder.base64">
-            <maven-resource group="net.iharder" artifact="base64"/>
-        </module-def>
-
 
         <!-- 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..f763148 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,8 +13,8 @@
         <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>
 
 </module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index 545f168..dab8040 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
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..39146d0 100755
--- a/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml
+++ b/distribution/adapters/wf9-adapter/wf9-adapter-zip/assembly.xml
@@ -11,9 +11,9 @@
         <fileSet>
             <directory>${project.build.directory}/unpacked</directory>
             <includes>
-                <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..9e4109a 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/build.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/build.xml
@@ -42,13 +42,14 @@
             <maven-resource group="org.keycloak" artifact="keycloak-core"/>
         </module-def>
 
-        <module-def name="net.iharder.base64">
-            <maven-resource group="net.iharder" artifact="base64"/>
-        </module-def>
-
 
         <!-- 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..74b361e 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,8 +13,8 @@
         <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>
 
 </module>
diff --git a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index 545f168..dab8040 100755
--- a/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/wf9-adapter/wf9-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
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..0a65ebd
--- 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,8 +13,8 @@
         <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>
 
 </module>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
index ce36b73..dccd315 100644
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
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/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
index ce36b73..dccd315 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core-jaxrs/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core-jaxrs/main/module.xml
index 359459c..e894d32 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core-jaxrs/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core-jaxrs/main/module.xml
@@ -13,7 +13,6 @@
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-api/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-api/main/module.xml
index f93fac0..deeec3e 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-api/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-api/main/module.xml
@@ -17,7 +17,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-dir/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-dir/main/module.xml
index e07d83a..760221a 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-dir/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-dir/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-single-file/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-single-file/main/module.xml
index 75bfd6a..f5c0478 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-single-file/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-single-file/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-zip/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-zip/main/module.xml
index ef9dfb3..94278c6 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-zip/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-export-import-zip/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="de.idyl.winzipaes"/>
         <module name="javax.api"/>
     </dependencies>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-invalidation-cache-model/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
index 8136f83..a9dc766 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
@@ -13,7 +13,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-kerberos-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-kerberos-federation/main/module.xml
index 620c797..8d63396 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-kerberos-federation/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-kerberos-federation/main/module.xml
@@ -9,7 +9,6 @@
     <dependencies>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-model-api"/>
-        <module name="net.iharder.base64"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-api/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-api/main/module.xml
index 3560576..f74bf32 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-api/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-api/main/module.xml
@@ -10,7 +10,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-jpa/main/module.xml
index 4e62ca7..4e8eafe 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.keycloak.keycloak-model-api"/>
         <module name="org.keycloak.keycloak-connections-jpa" services="import"/>
         <module name="javax.persistence.api"/>
-        <module name="net.iharder.base64"/>
         <module name="org.jboss.logging"/>
         <module name="org.javassist"/>
         <module name="org.hibernate" services="import"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-mongo/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-mongo/main/module.xml
index 9c99b57..1b8b892 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-mongo/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-mongo/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.keycloak.keycloak-model-api"/>
         <module name="org.keycloak.keycloak-connections-mongo"/>
         <module name="org.mongodb.mongo-java-driver"/>
-        <module name="net.iharder.base64"/>
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
     </dependencies>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
index 052383d..d3469c1 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
@@ -72,7 +72,6 @@
         <module name="com.google.zxing.javase"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="org.apache.httpcomponents"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 8f9ba8b..0989c39 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -42,10 +42,6 @@
             <maven-resource group="org.keycloak" artifact="keycloak-core"/>
         </module-def>
 
-        <module-def name="net.iharder.base64">
-            <maven-resource group="net.iharder" artifact="base64"/>
-        </module-def>
-
         <module-def name="org.bouncycastle">
             <maven-resource group="org.bouncycastle" artifact="bcprov-jdk15on"/>
             <maven-resource group="org.bouncycastle" artifact="bcpkix-jdk15on"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index 545f168..dab8040 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="sun.jdk" optional="true" />
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core-jaxrs/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core-jaxrs/main/module.xml
index e8b0e5c..68d8e5b 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core-jaxrs/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-core-jaxrs/main/module.xml
@@ -13,7 +13,6 @@
         <module name="org.codehaus.jackson.jackson-xc"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-api/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-api/main/module.xml
index 7ff6b07..329e089 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-api/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-api/main/module.xml
@@ -17,7 +17,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-dir/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-dir/main/module.xml
index 046e436..cdb3536 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-dir/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-dir/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-single-file/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-single-file/main/module.xml
index 9940d3e..6470043 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-single-file/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-single-file/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-zip/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-zip/main/module.xml
index 2e28fbb..6f672dc 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-zip/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-export-import-zip/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="de.idyl.winzipaes"/>
         <module name="javax.api"/>
     </dependencies>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-invalidation-cache-model/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
index b482069..5486133 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-invalidation-cache-model/main/module.xml
@@ -13,7 +13,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-kerberos-federation/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-kerberos-federation/main/module.xml
index a238b9c..7f610a6 100644
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-kerberos-federation/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-kerberos-federation/main/module.xml
@@ -9,7 +9,6 @@
     <dependencies>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-model-api"/>
-        <module name="net.iharder.base64"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-api/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-api/main/module.xml
index c5902a8..f19e0d3 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-api/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-api/main/module.xml
@@ -10,7 +10,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
     </dependencies>
 
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-jpa/main/module.xml
index 88cdce9..379d5fb 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.keycloak.keycloak-model-api"/>
         <module name="org.keycloak.keycloak-connections-jpa" services="import"/>
         <module name="javax.persistence.api"/>
-        <module name="net.iharder.base64"/>
         <module name="org.jboss.logging"/>
         <module name="org.javassist"/>
         <module name="org.hibernate" services="import"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-mongo/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-mongo/main/module.xml
index 205a30b..bee88dd 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-mongo/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-mongo/main/module.xml
@@ -11,7 +11,6 @@
         <module name="org.keycloak.keycloak-model-api"/>
         <module name="org.keycloak.keycloak-connections-mongo"/>
         <module name="org.mongodb.mongo-java-driver"/>
-        <module name="net.iharder.base64"/>
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
     </dependencies>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 64915d3..cbfe05e 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -72,7 +72,6 @@
         <module name="com.google.zxing.javase"/>
         <module name="org.jboss.logging"/>
         <module name="org.bouncycastle" />
-        <module name="net.iharder.base64"/>
         <module name="javax.api"/>
         <module name="javax.activation.api"/>
         <module name="org.apache.httpcomponents"/>
diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml
index 04b9ca8..fc44079 100755
--- a/distribution/server-overlay/wf9-server-overlay/assembly.xml
+++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml
@@ -15,7 +15,6 @@
             <includes>
                 <include>com/google/zxing/**</include>
                 <include>de/idyl/winzipaes/**</include>
-                <include>net/iharder/**</include>
                 <include>org/freemarker/**</include>
                 <include>org/keycloak/**</include>
                 <include>org/liquibase/**</include>
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/export-import/export-import-api/pom.xml b/export-import/export-import-api/pom.xml
index 734c793..be9e8f4 100755
--- a/export-import/export-import-api/pom.xml
+++ b/export-import/export-import-api/pom.xml
@@ -44,11 +44,6 @@
             <artifactId>jackson-mapper-asl</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
     </dependencies>
 
     <build>
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 5db42fc..5137491 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -1,6 +1,6 @@
 package org.keycloak.exportimport.util;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 import org.codehaus.jackson.JsonEncoding;
 import org.codehaus.jackson.JsonFactory;
 import org.codehaus.jackson.JsonGenerator;
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index ce6c8fa..44e74be 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -28,11 +28,6 @@
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
     </dependencies>
 
     <build>
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
index 2252a87..beaebf2 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
@@ -5,7 +5,7 @@ import java.security.PrivilegedExceptionAction;
 
 import javax.security.auth.Subject;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
diff --git a/integration/adapter-core/pom.xml b/integration/adapter-core/pom.xml
index 973003e..e2f6977 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>
@@ -59,11 +64,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
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/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
index 5ce1b6e..e16137b 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
@@ -50,7 +50,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
 
         AccessTokenResponse atr=null;        
         try {
-            String userpw=new String(net.iharder.Base64.decode(tokenString));
+            String userpw=new String(org.keycloak.util.Base64.decode(tokenString));
             String[] parts=userpw.split(":");
             
             atr = getToken(parts[0], parts[1]);
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..9b938f4 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>
@@ -31,10 +35,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>
diff --git a/integration/installed/pom.xml b/integration/installed/pom.xml
index 379f7b6..cb13d90 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>
@@ -31,10 +35,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-core-asl</artifactId>
         </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..50f0071 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>
@@ -33,10 +37,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>
diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml
index 81625f4..753f581 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>
@@ -47,10 +51,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</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/pom.xml b/integration/jetty/jetty9.1/pom.xml
index 85d61d8..576031a 100755
--- a/integration/jetty/jetty9.1/pom.xml
+++ b/integration/jetty/jetty9.1/pom.xml
@@ -62,10 +62,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
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/pom.xml b/integration/jetty/jetty9.2/pom.xml
index ab79f28..9695e76 100755
--- a/integration/jetty/jetty9.2/pom.xml
+++ b/integration/jetty/jetty9.2/pom.xml
@@ -48,10 +48,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
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..f8691b3 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>
@@ -44,10 +52,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
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..69a5306 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>
@@ -35,11 +39,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.jboss.spec.javax.servlet</groupId>
             <artifactId>jboss-servlet-api_3.0_spec</artifactId>
             <scope>provided</scope>
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..0c87150 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>
@@ -59,12 +63,6 @@
             <version>${apache-httpcomponents.version}</version>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <version>${base64.version}</version>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
             <version>${bouncycastle.crypto.version}</version>
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/tomcat6/pom.xml b/integration/tomcat/tomcat6/pom.xml
index f2313e9..b27e480 100755
--- a/integration/tomcat/tomcat6/pom.xml
+++ b/integration/tomcat/tomcat6/pom.xml
@@ -53,10 +53,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
diff --git a/integration/tomcat/tomcat7/pom.xml b/integration/tomcat/tomcat7/pom.xml
index 04aabd8..8056d56 100755
--- a/integration/tomcat/tomcat7/pom.xml
+++ b/integration/tomcat/tomcat7/pom.xml
@@ -54,10 +54,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
diff --git a/integration/tomcat/tomcat8/pom.xml b/integration/tomcat/tomcat8/pom.xml
index 1b59514..92a4127 100755
--- a/integration/tomcat/tomcat8/pom.xml
+++ b/integration/tomcat/tomcat8/pom.xml
@@ -66,10 +66,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
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..ea842ef 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>
@@ -37,10 +45,6 @@
 			<artifactId>httpclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.iharder</groupId>
-			<artifactId>base64</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
 		</dependency>
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..e96b03c 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>
@@ -33,10 +41,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </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..1a96944 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>
@@ -41,10 +49,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>
diff --git a/model/api/pom.xml b/model/api/pom.xml
index 58ca5e2..9f5c8af 100755
--- a/model/api/pom.xml
+++ b/model/api/pom.xml
@@ -20,11 +20,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
             <scope>provided</scope>
diff --git a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
index 32f0d15..6e95490 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
@@ -1,6 +1,6 @@
 package org.keycloak.models.utils;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.PBEKeySpec;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 77ae66e..8a79f01 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1,6 +1,6 @@
 package org.keycloak.models.utils;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 import org.jboss.logging.Logger;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.migration.MigrationProvider;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
index bc770ca..a316a48 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
@@ -1,6 +1,6 @@
 package org.keycloak.models.utils;
 
-import net.iharder.Base64;
+import org.keycloak.util.Base64;
 
 import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
diff --git a/model/invalidation-cache/model-adapters/pom.xml b/model/invalidation-cache/model-adapters/pom.xml
index 0f45b87..f25c697 100755
--- a/model/invalidation-cache/model-adapters/pom.xml
+++ b/model/invalidation-cache/model-adapters/pom.xml
@@ -20,11 +20,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-core</artifactId>
             <scope>provided</scope>
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 4013fca..df926f9 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -19,10 +19,6 @@
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-core</artifactId>
         </dependency>
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index 0c53237..7bf62a9 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -21,11 +21,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-core</artifactId>
             <scope>provided</scope>

pom.xml 36(+30 -6)

diff --git a/pom.xml b/pom.xml
index bf0e5d6..36e8e3e 100755
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,6 @@
         <apacheds.version>2.0.0-M17</apacheds.version>
         <apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
         <org.apache.james.apache-mime4j.version>0.6</org.apache.james.apache-mime4j.version>
-        <base64.version>2.3.8</base64.version>
         <bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
         <jackson.version>1.9.9</jackson.version>
         <apache.httpcomponents.version>4.3.6</apache.httpcomponents.version>
@@ -191,11 +190,6 @@
                 <version>${bouncycastle.crypto.version}</version>
             </dependency>
             <dependency>
-                <groupId>net.iharder</groupId>
-                <artifactId>base64</artifactId>
-                <version>${base64.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>javax.mail</groupId>
                 <artifactId>mail</artifactId>
                 <version>${javax.mail.version}</version>
@@ -756,6 +750,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 +785,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 +860,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 +885,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 +970,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/proxy/proxy-server/pom.xml b/proxy/proxy-server/pom.xml
index eb077ce..d7047b7 100755
--- a/proxy/proxy-server/pom.xml
+++ b/proxy/proxy-server/pom.xml
@@ -35,10 +35,6 @@
             <artifactId>httpclient</artifactId>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </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/jaxrs/JaxrsBasicAuthTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java
index 6cd7998..19105bc 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java
@@ -138,6 +138,6 @@ public class JaxrsBasicAuthTest {
 
     private String encodeCredentials(String username, String password) {
         String text=username+":"+password;
-        return (net.iharder.Base64.encodeBytes(text.getBytes()));
+        return (org.keycloak.util.Base64.encodeBytes(text.getBytes()));
     }
 }
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"
             }
         }
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 9ac97d0..8f900b9 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -209,6 +209,10 @@
                     <groupId>org.keycloak</groupId>
                     <artifactId>keycloak-adapter-core</artifactId>
                 </dependency>
+                <dependency>
+                    <groupId>org.keycloak</groupId>
+                    <artifactId>keycloak-adapter-spi</artifactId>
+                </dependency>
 
 
                 <!-- Keycloak Server on Undertow -->
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index 03bf3fe..0842cab 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -64,10 +64,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
         </dependency>