keycloak-aplcache

Merge pull request #937 from patriot1burke/master saml

1/28/2015 8:34:36 PM

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index faee3c8..82c2ebc 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -22,6 +22,7 @@ public class ApplicationRepresentation {
     protected Integer notBefore;
     protected Boolean bearerOnly;
     protected Boolean publicClient;
+    protected Boolean frontchannelLogout;
     protected String protocol;
     protected Map<String, String> attributes;
     protected Boolean fullScopeAllowed;
@@ -179,4 +180,12 @@ public class ApplicationRepresentation {
     public void setRegisteredNodes(Map<String, Integer> registeredNodes) {
         this.registeredNodes = registeredNodes;
     }
+
+    public Boolean isFrontchannelLogout() {
+        return frontchannelLogout;
+    }
+
+    public void setFrontchannelLogout(Boolean frontchannelLogout) {
+        this.frontchannelLogout = frontchannelLogout;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
index e61cdfc..de65ae6 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -21,6 +21,7 @@ public class OAuthClientRepresentation {
     protected Map<String, String> attributes;
     protected Boolean directGrantsOnly;
     protected Boolean fullScopeAllowed;
+    protected Boolean frontchannelLogout;
 
 
     public String getId() {
@@ -126,4 +127,12 @@ public class OAuthClientRepresentation {
     public void setAttributes(Map<String, String> attributes) {
         this.attributes = attributes;
     }
+
+    public Boolean isFrontchannelLogout() {
+        return frontchannelLogout;
+    }
+
+    public void setFrontchannelLogout(Boolean frontchannelLogout) {
+        this.frontchannelLogout = frontchannelLogout;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index a96e217..42c4423 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -230,6 +230,7 @@ public class ModelToRepresentation {
         rep.setEnabled(applicationModel.isEnabled());
         rep.setAdminUrl(applicationModel.getManagementUrl());
         rep.setPublicClient(applicationModel.isPublicClient());
+        rep.setFrontchannelLogout(applicationModel.isFrontchannelLogout());
         rep.setProtocol(applicationModel.getProtocol());
         rep.setAttributes(applicationModel.getAttributes());
         rep.setFullScopeAllowed(applicationModel.isFullScopeAllowed());
@@ -266,6 +267,7 @@ public class ModelToRepresentation {
         rep.setName(model.getClientId());
         rep.setEnabled(model.isEnabled());
         rep.setPublicClient(model.isPublicClient());
+        rep.setFrontchannelLogout(model.isFrontchannelLogout());
         rep.setProtocol(model.getProtocol());
         rep.setAttributes(model.getAttributes());
         rep.setFullScopeAllowed(model.isFullScopeAllowed());
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 50907bc..aede6a2 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
@@ -389,6 +389,7 @@ public class RepresentationToModel {
         applicationModel.setBaseUrl(resourceRep.getBaseUrl());
         if (resourceRep.isBearerOnly() != null) applicationModel.setBearerOnly(resourceRep.isBearerOnly());
         if (resourceRep.isPublicClient() != null) applicationModel.setPublicClient(resourceRep.isPublicClient());
+        if (resourceRep.isFrontchannelLogout() != null) applicationModel.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
         if (resourceRep.getProtocol() != null) applicationModel.setProtocol(resourceRep.getProtocol());
         if (resourceRep.isFullScopeAllowed() != null) {
             applicationModel.setFullScopeAllowed(resourceRep.isFullScopeAllowed());
@@ -475,6 +476,7 @@ public class RepresentationToModel {
         if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
         if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
         if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
+        if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
         if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
         if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
         if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
@@ -596,6 +598,7 @@ public class RepresentationToModel {
         if (rep.getName() != null) model.setClientId(rep.getName());
         if (rep.isEnabled() != null) model.setEnabled(rep.isEnabled());
         if (rep.isPublicClient() != null) model.setPublicClient(rep.isPublicClient());
+        if (rep.isFrontchannelLogout() != null) model.setFrontchannelLogout(rep.isFrontchannelLogout());
         if (rep.isFullScopeAllowed() != null) model.setFullScopeAllowed(rep.isFullScopeAllowed());
         if (rep.isDirectGrantsOnly() != null) model.setDirectGrantsOnly(rep.isDirectGrantsOnly());
         if (rep.getClaims() != null) {

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 8e645aa..7ae9a07 100755
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
         <resteasy.version.latest>3.0.9.Final</resteasy.version.latest>
         <!-- <undertow.version>1.1.0.Final</undertow.version> -->
         <undertow.version>1.1.1.Final</undertow.version>
-        <picketlink.version>2.7.0.CR2</picketlink.version>
+        <picketlink.version>2.7.0.CR3</picketlink.version>
         <picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
         <mongo.driver.version>2.11.3</mongo.driver.version>
         <jboss.logging.version>3.1.4.GA</jboss.logging.version>
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
index 84cf7ee..d17c724 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2BindingBuilder.java
@@ -1,5 +1,6 @@
 package org.keycloak.protocol.saml;
 
+import org.jboss.logging.Logger;
 import org.picketlink.common.constants.GeneralConstants;
 import org.picketlink.common.constants.JBossSAMLConstants;
 import org.picketlink.common.constants.JBossSAMLURIConstants;
@@ -38,6 +39,7 @@ import static org.picketlink.common.util.StringUtil.isNotNull;
  * @version $Revision: 1 $
  */
 public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
+    protected static final Logger logger = Logger.getLogger(SAML2BindingBuilder.class);
 
     protected KeyPair signingKeyPair;
     protected X509Certificate signingCertificate;
@@ -148,6 +150,9 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
         public Response request() throws ConfigurationException, ProcessingException, IOException {
             return buildResponse(document, destination, true);
         }
+        public Response request(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+            return buildResponse(document, actionUrl, true);
+        }
         public Response response() throws ConfigurationException, ProcessingException, IOException {
             return buildResponse(document, destination, false);
         }
@@ -186,13 +191,16 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
             return response(destination, false);
         }
 
+        public Response request(String redirect) throws ProcessingException, ConfigurationException, IOException {
+            return response(redirect, true);
+        }
         public Response request() throws ProcessingException, ConfigurationException, IOException {
             return response(destination, true);
         }
 
         private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
             URI uri = responseUri(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)
@@ -339,7 +347,9 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
     }
 
     protected String base64Encoded(Document document) throws ConfigurationException, ProcessingException, IOException  {
-        byte[] responseBytes = org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil.getDocumentAsString(document).getBytes("UTF-8");
+        String documentAsString = org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil.getDocumentAsString(document);
+        logger.debugv("saml docment: {0}", documentAsString);
+        byte[] responseBytes = documentAsString.getBytes("UTF-8");
 
         return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
     }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
index 65c902c..1d76e2f 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
@@ -53,6 +53,13 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
         nameID.setFormat(URI.create(nameIDFormat));
         lort.setNameID(nameID);
 
+        if (issuer != null) {
+            NameIDType issuerID = new NameIDType();
+            issuerID.setValue(issuer);
+            lort.setIssuer(issuerID);
+        }
+
+
         long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
 
         lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
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 f3bc382..ede181d 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
@@ -307,9 +307,10 @@ public class SamlProtocol implements LoginProtocol {
         SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
         try {
             if (isLogoutPostBindingForClient(app)) {
-                return logoutBuilder.postBinding().response(bindingUri);
+                return logoutBuilder.postBinding().request(bindingUri);
             } else {
-                return logoutBuilder.redirectBinding().response(bindingUri);
+                logger.debug("frontchannel redirect binding");
+                return logoutBuilder.redirectBinding().request(bindingUri);
             }
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
@@ -325,6 +326,7 @@ public class SamlProtocol implements LoginProtocol {
 
     @Override
     public Response finishLogout(UserSessionModel userSession) {
+        logger.debug("finishLogout");
         SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
         builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
         builder.destination(userSession.getNote(SAML_LOGOUT_ISSUER));
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 e4bacf9..4320f4e 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
@@ -136,6 +136,7 @@ public class SamlService {
                 event.error(Errors.INVALID_TOKEN);
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
             }
+            logger.debug("logout response");
             return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
         }
 
@@ -183,12 +184,15 @@ public class SamlService {
                 event.error(Errors.INVALID_SIGNATURE);
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.");
             }
+            logger.debug("verified request");
             if (samlObject instanceof AuthnRequestType) {
+                logger.debug("** login request");
                 event.event(EventType.LOGIN);
                 // Get the SAML Request Message
                 AuthnRequestType authn = (AuthnRequestType) samlObject;
                 return loginRequest(relayState, authn, client);
             } else if (samlObject instanceof LogoutRequestType) {
+                logger.debug("** logout request");
                 event.event(EventType.LOGOUT);
                 LogoutRequestType logout = (LogoutRequestType) samlObject;
                 return logoutRequest(logout, client, relayState);
@@ -306,6 +310,13 @@ public class SamlService {
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_ISSUER, logoutRequest.getIssuer().getValue());
                 userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
+                // remove client from logout requests
+                for (ClientSessionModel clientSession : userSession.getClientSessions()) {
+                    if (clientSession.getClient().getId().equals(client.getId())) {
+                        clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
+                    }
+                }
+                logger.debug("browser Logout");
                 return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
             }
 
@@ -450,6 +461,7 @@ public class SamlService {
     public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
                                     @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
                                     @QueryParam(GeneralConstants.RELAY_STATE) String relayState)  {
+        logger.debug("SAML GET");
         return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
     }
 
@@ -461,6 +473,7 @@ public class SamlService {
     public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
                                 @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
                                 @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
+        logger.debug("SAML POST");
         return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 51df254..d02ad2b 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -115,19 +115,21 @@ public class AuthenticationManager {
         List<ClientSessionModel> redirectClients = new LinkedList<ClientSessionModel>();
         for (ClientSessionModel clientSession : userSession.getClientSessions()) {
             ClientModel client = clientSession.getClient();
+            if (clientSession.getAction() == ClientSessionModel.Action.LOGGED_OUT) continue;
             if (client.isFrontchannelLogout()) {
                 String authMethod = clientSession.getAuthMethod();
                 if (authMethod == null) continue; // must be a keycloak service like account
                 redirectClients.add(clientSession);
                 continue;
             }
-            if (client instanceof ApplicationModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
+            if (client instanceof ApplicationModel && !client.isFrontchannelLogout()) {
                 String authMethod = clientSession.getAuthMethod();
                 if (authMethod == null) continue; // must be a keycloak service like account
                 LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
                 protocol.setRealm(realm)
                         .setUriInfo(uriInfo);
                 try {
+                    logger.debugv("backchannel logout to: {0}", client.getClientId());
                     protocol.backchannelLogout(userSession, clientSession);
                     clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
                 } catch (Exception e) {
@@ -147,8 +149,12 @@ public class AuthenticationManager {
             // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not
             nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT);
             try {
+                logger.debugv("frontchannel logout to: {0}", nextRedirectClient.getClient().getClientId());
                 Response response = protocol.frontchannelLogout(userSession, nextRedirectClient);
-                if (response != null) return response;
+                if (response != null) {
+                    logger.debug("returning frontchannel logout request to client");
+                    return response;
+                }
             } catch (Exception e) {
                 logger.warn("Failed to logout client, continuing", e);
             }
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 55f4726..25a3414 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -328,7 +328,7 @@ public class ResourceAdminManager {
             return new GlobalRequestResult();
         }
 
-        if (logger.isDebugEnabled()) logger.info("Sending push revocation to URLS: " + mgmtUrls);
+        if (logger.isDebugEnabled()) logger.debug("Sending push revocation to URLS: " + mgmtUrls);
 
         // Propagate this to all hosts
         GlobalRequestResult result = new GlobalRequestResult();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index d633120..b600305 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -54,6 +54,7 @@ public class SamlBindingTest {
             initializeSamlSecuredWar("/saml/signed-post-persistent", "/sales-post-sig-persistent",  "post-sig-persistent.war", classLoader);
             initializeSamlSecuredWar("/saml/signed-metadata", "/sales-metadata",  "post-metadata.war", classLoader);
             initializeSamlSecuredWar("/saml/signed-get", "/employee-sig",  "employee-sig.war", classLoader);
+            initializeSamlSecuredWar("/saml/signed-front-get", "/employee-sig-front",  "employee-sig-front.war", classLoader);
             initializeSamlSecuredWar("/saml/bad-client-signed-post", "/bad-client-sales-post-sig",  "bad-client-post-sig.war", classLoader);
             initializeSamlSecuredWar("/saml/bad-realm-signed-post", "/bad-realm-sales-post-sig",  "bad-realm-post-sig.war", classLoader);
             initializeSamlSecuredWar("/saml/encrypted-post", "/sales-post-enc",  "post-enc.war", classLoader);
@@ -79,9 +80,14 @@ public class SamlBindingTest {
         Thread.sleep(10000000);
     }
 
-    protected void checkLoggedOut() {
-        Assert.assertTrue(driver.getPageSource().contains("request-path: /logout.jsp"));
-        Assert.assertTrue(driver.getPageSource().contains("principal=null"));
+    protected void checkLoggedOut(String mainUrl) {
+        String pageSource = driver.getPageSource();
+        System.out.println("*** logout pagesouce ***");
+        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"));
     }
 
 
@@ -94,7 +100,7 @@ public class SamlBindingTest {
         System.out.println(driver.getPageSource());
         Assert.assertTrue(driver.getPageSource().contains("bburke"));
         driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
-        checkLoggedOut();
+        checkLoggedOut("http://localhost:8081/sales-post/");
     }
     @Test
     public void testPostSignedLoginLogout() {
@@ -104,7 +110,7 @@ public class SamlBindingTest {
         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();
+        checkLoggedOut("http://localhost:8081/sales-post-sig/");
 
     }
     @Test
@@ -117,7 +123,7 @@ public class SamlBindingTest {
         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();
+        checkLoggedOut("http://localhost:8081/sales-post-sig-transient/");
 
     }
     @Test
@@ -130,7 +136,7 @@ public class SamlBindingTest {
         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();
+        checkLoggedOut("http://localhost:8081/sales-post-sig-persistent/");
 
     }
     @Test
@@ -142,7 +148,7 @@ public class SamlBindingTest {
         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();
+        checkLoggedOut("http://localhost:8081/sales-post-sig-email/");
 
     }
     @Test
@@ -153,7 +159,45 @@ public class SamlBindingTest {
         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();
+        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"));
+
+        // 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/");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
 
     }
 
@@ -165,7 +209,7 @@ public class SamlBindingTest {
         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();
+        checkLoggedOut("http://localhost:8081/sales-post-enc/");
 
     }
     @Test
@@ -213,7 +257,7 @@ public class SamlBindingTest {
         String pageSource = driver.getPageSource();
         Assert.assertTrue(pageSource.contains("bburke"));
         driver.navigate().to("http://localhost:8081/sales-metadata?GLO=true");
-        checkLoggedOut();
+        checkLoggedOut("http://localhost:8081/sales-metadata/");
 
     }
 
diff --git a/testsuite/integration/src/test/resources/saml/signed-front-get/WEB-INF/keystore.jks b/testsuite/integration/src/test/resources/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/saml/signed-front-get/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration/src/test/resources/saml/signed-front-get/WEB-INF/picketlink.xml b/testsuite/integration/src/test/resources/saml/signed-front-get/WEB-INF/picketlink.xml
new file mode 100755
index 0000000..06fff8c
--- /dev/null
+++ b/testsuite/integration/src/test/resources/saml/signed-front-get/WEB-INF/picketlink.xml
@@ -0,0 +1,38 @@
+<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
+	<PicketLinkSP xmlns="urn:picketlink:identity-federation:config:2.1"
+		ServerEnvironment="tomcat" BindingType="REDIRECT" SupportsSignatures="true" LogOutResponseLocation="${idp-sig.url::http://localhost:8081/auth/realms/demo/protocol/saml}">
+		<IdentityURL>${idp-sig.url::http://localhost:8081/auth/realms/demo/protocol/saml}
+		</IdentityURL>
+		<ServiceURL>${employee-sig.url::http://localhost:8081/employee-sig-front/}
+		</ServiceURL>
+		<KeyProvider
+			ClassName="org.picketlink.identity.federation.core.impl.KeyStoreKeyManager">
+			<Auth Key="KeyStoreURL" Value="saml/signed-front-get/WEB-INF/keystore.jks" />
+			<Auth Key="KeyStorePass" Value="store123" />
+			<Auth Key="SigningKeyPass" Value="test123" />
+			<Auth Key="SigningKeyAlias" Value="http://localhost:8080/employee-sig/" />
+			<ValidatingAlias Key="localhost" Value="demo" />
+			<ValidatingAlias Key="127.0.0.1" Value="demo" />
+		</KeyProvider>
+	</PicketLinkSP>
+	<Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1">
+		<Handler
+			class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler" />
+        <Handler
+                class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
+            <Option Key="NAMEID_FORMAT" Value="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>
+        </Handler>
+		<Handler
+			class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
+		<Handler
+            class="org.picketlink.identity.federation.web.handlers.saml2.SAML2SignatureGenerationHandler">
+            <!--
+                This is a optional configuration. By default, method http://www.w3.org/2000/09/xmldsig#rsa-sha1
+                and digest http://www.w3.org/2000/09/xmldsig#sha1 are used. -->
+            <Option Key="SIGN_METHOD" Value="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+            <Option Key="SIGN_DIGEST" Value="http://www.w3.org/2001/04/xmlenc#sha256"/>
+        </Handler>
+        <Handler
+            class="org.picketlink.identity.federation.web.handlers.saml2.SAML2SignatureValidationHandler" />
+	</Handlers>
+</PicketLink>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index 4453c34..0e31188 100755
--- a/testsuite/integration/src/test/resources/saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/saml/testsaml.json
@@ -194,6 +194,26 @@
                 "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"
             }
+        },
+        {
+            "name": "http://localhost:8081/employee-sig-front/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "frontchannelLogout": true,
+            "baseUrl": "http://localhost:8081/employee-sig-front",
+            "adminUrl": "http://localhost:8081/employee-sig-front",
+            "redirectUris": [
+                "http://localhost:8081/employee-sig-front/*"
+            ],
+            "attributes": {
+                "saml.server.signature": "true",
+                "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"
+            }
         }
     ],
     "roles" : {