keycloak-aplcache

undertow logout

12/19/2013 9:32:47 PM

Details

diff --git a/core/src/main/java/org/keycloak/util/StreamUtil.java b/core/src/main/java/org/keycloak/util/StreamUtil.java
new file mode 100755
index 0000000..428a590
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/StreamUtil.java
@@ -0,0 +1,31 @@
+package org.keycloak.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StreamUtil {
+    public static String readString(InputStream in) throws IOException
+    {
+        char[] buffer = new char[1024];
+        StringBuilder builder = new StringBuilder();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        int wasRead = 0;
+        do
+        {
+            wasRead = reader.read(buffer, 0, 1024);
+            if (wasRead > 0)
+            {
+                builder.append(buffer, 0, wasRead);
+            }
+        }
+        while (wasRead > -1);
+
+        return builder.toString();
+    }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
index 469ab06..76fdf4b 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java
@@ -12,11 +12,10 @@ import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.KeycloakUriBuilder;
+import org.keycloak.util.StreamUtil;
 
-import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -89,32 +88,12 @@ public class TokenGrantRequest {
     }
 
 
-    protected static String readString(InputStream in) throws IOException
-    {
-        char[] buffer = new char[1024];
-        StringBuilder builder = new StringBuilder();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        int wasRead = 0;
-        do
-        {
-            wasRead = reader.read(buffer, 0, 1024);
-            if (wasRead > 0)
-            {
-                builder.append(buffer, 0, wasRead);
-            }
-        }
-        while (wasRead > -1);
-
-        return builder.toString();
-    }
-
-
     protected static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
        String body = null;
         if (entity != null) {
             InputStream is = entity.getContent();
             try {
-                body = readString(is);
+                body = StreamUtil.readString(is);
             } catch (IOException e) {
 
             } finally {
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticatorValve.java
index c218d36..b8b0688 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticatorValve.java
@@ -26,9 +26,11 @@ import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.representations.adapters.action.LogoutAction;
 import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.StreamUtil;
 
 import javax.security.auth.login.LoginException;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.HashSet;
@@ -120,8 +122,8 @@ public class OAuthAuthenticatorValve extends FormAuthenticator implements Lifecy
         return false;
     }
 
-    protected JWSInput verifyAdminRequest(Request request, HttpServletResponse response) throws IOException {
-        String token = request.getParameter("token");
+    protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        String token = StreamUtil.readString(request.getInputStream());
         if (token == null) {
             log.warn("admin request failed, no token");
             response.sendError(403, "no token");
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
index 25a2e7d..120546f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
@@ -32,16 +32,21 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
     protected RealmConfiguration realmConfig;
     protected int sslRedirectPort;
 
-    public KeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, AdapterConfig config, RealmConfiguration realmConfig, int sslRedirectPort) {
-        this.resourceMetadata = resourceMetadata;
+    public KeycloakAuthenticationMechanism(AdapterConfig config, RealmConfiguration realmConfig, int sslRedirectPort) {
+        this.resourceMetadata = realmConfig.getMetadata();
         this.adapterConfig = config;
         this.realmConfig = realmConfig;
         this.sslRedirectPort = sslRedirectPort;
     }
 
-    public KeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, AdapterConfig config, RealmConfiguration realmConfig) {
+    public KeycloakAuthenticationMechanism(AdapterConfig adapterConfig, ResourceMetadata resourceMetadata) {
         this.resourceMetadata = resourceMetadata;
-        this.adapterConfig = config;
+        this.adapterConfig = adapterConfig;
+    }
+
+    public KeycloakAuthenticationMechanism(AdapterConfig adapterConfig, RealmConfiguration realmConfig) {
+        this.resourceMetadata = realmConfig.getMetadata();
+        this.adapterConfig = adapterConfig;
         this.realmConfig = realmConfig;
     }
 
@@ -57,8 +62,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
             final SkeletonKeyToken token = bearer.getToken();
             String surrogate = bearer.getSurrogate();
             SkeletonKeySession session = new SkeletonKeySession(bearer.getTokenString(), token, resourceMetadata);
-            propagateBearer(exchange, session);
-            completeAuthentication(exchange, securityContext, token, surrogate);
+            SkeletonKeyPrincipal principal = completeAuthentication(securityContext, token, surrogate);
+            propagateBearer(exchange, session, principal);
             return AuthenticationMechanismOutcome.AUTHENTICATED;
         }
         else if (adapterConfig.isBearerOnly()) {
@@ -78,8 +83,8 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
 
         }
         SkeletonKeySession session = new SkeletonKeySession(oauth.getTokenString(), oauth.getToken(), resourceMetadata);
-        propagateOauth(exchange, session);
-        completeAuthentication(exchange, securityContext, oauth.getToken(), null);
+        SkeletonKeyPrincipal principal = completeAuthentication(securityContext, oauth.getToken(), null);
+        propagateOauth(exchange, session, principal);
         log.info("AUTHENTICATED");
         return AuthenticationMechanismOutcome.AUTHENTICATED;
     }
@@ -92,7 +97,7 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
         return new BearerTokenAuthenticator(resourceMetadata, adapterConfig.isUseResourceRoleMappings());
     }
 
-    protected void completeAuthentication(HttpServerExchange exchange, SecurityContext securityContext, SkeletonKeyToken token, String surrogate) {
+    protected SkeletonKeyPrincipal completeAuthentication(SecurityContext securityContext, SkeletonKeyToken token, String surrogate) {
         final SkeletonKeyPrincipal skeletonKeyPrincipal = new SkeletonKeyPrincipal(token.getPrincipal(), surrogate);
         Set<String> roles = null;
         if (adapterConfig.isUseResourceRoleMappings()) {
@@ -116,14 +121,15 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism 
             }
         };
         securityContext.authenticationComplete(account, "KEYCLOAK", true);
+        return skeletonKeyPrincipal;
     }
 
-    protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession session) {
+    protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession session, SkeletonKeyPrincipal principal) {
         exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
 
     }
 
-    protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession session) {
+    protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession session, SkeletonKeyPrincipal principal) {
         exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
     }
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 0527d38..52eb239 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -12,6 +12,7 @@ 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.config.RealmConfiguration;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.adapters.config.RealmConfigurationLoader;
 
@@ -52,20 +53,30 @@ public class KeycloakServletExtension implements ServletExtension {
         RealmConfigurationLoader loader = new RealmConfigurationLoader(is);
         loader.init(true);
         AdapterConfig keycloakConfig = loader.getAdapterConfig();
+        RealmConfiguration realmConfiguration = loader.getRealmConfiguration();
         PreflightCorsHandler.Wrapper preflight = new PreflightCorsHandler.Wrapper(keycloakConfig);
-        final ServletKeycloakAuthenticationMechanism auth = new ServletKeycloakAuthenticationMechanism(loader.getResourceMetadata(),
+        UserSessionManagement userSessionManagement = new UserSessionManagement(realmConfiguration);
+        ServletKeycloakAuthenticationMechanism auth = null;
+        if (keycloakConfig.isBearerOnly()) {
+            auth = new ServletKeycloakAuthenticationMechanism(keycloakConfig, loader.getResourceMetadata(), deploymentInfo.getConfidentialPortManager());
+        } else {
+            auth = new ServletKeycloakAuthenticationMechanism(
+                userSessionManagement,
                 keycloakConfig,
-                loader.getRealmConfiguration(),
+                realmConfiguration,
                 deploymentInfo.getConfidentialPortManager());
+        }
         ServletAuthenticatedActionsHandler.Wrapper actions = new ServletAuthenticatedActionsHandler.Wrapper(keycloakConfig);
 
         // setup handlers
 
         deploymentInfo.addInitialHandlerChainWrapper(preflight); // cors preflight
+        deploymentInfo.addOuterHandlerChainWrapper(new ServletAdminActionsHandler.Wrapper(realmConfiguration, userSessionManagement));
+        final ServletKeycloakAuthenticationMechanism theAuth = auth;
         deploymentInfo.addAuthenticationMechanism("KEYCLOAK", new AuthenticationMechanismFactory() {
             @Override
             public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
-                return auth;
+                return theAuth;
             }
         }); // authentication
         deploymentInfo.addInnerHandlerChainWrapper(ServletPropagateSessionHandler.WRAPPER); // propagates SkeletonKeySession
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java
new file mode 100755
index 0000000..4bfacca
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAdminActionsHandler.java
@@ -0,0 +1,95 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterAdminResourceConstants;
+import org.keycloak.adapters.config.RealmConfiguration;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.util.StreamUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletAdminActionsHandler implements HttpHandler {
+
+    private static final Logger log = Logger.getLogger(ServletAdminActionsHandler.class);
+    protected HttpHandler next;
+    protected UserSessionManagement userSessionManagement;
+    protected RealmConfiguration realmConfig;
+
+    public static class Wrapper implements HandlerWrapper {
+        protected RealmConfiguration realmConfig;
+        protected UserSessionManagement userSessionManagement;
+
+        public Wrapper(RealmConfiguration realmConfig, UserSessionManagement userSessionManagement) {
+            this.realmConfig = realmConfig;
+            this.userSessionManagement = userSessionManagement;
+        }
+
+        @Override
+        public HttpHandler wrap(HttpHandler handler) {
+            return new ServletAdminActionsHandler(realmConfig, userSessionManagement, handler);
+        }
+    }
+
+    protected ServletAdminActionsHandler(RealmConfiguration realmConfig,
+                                         UserSessionManagement userSessionManagement,
+                                         HttpHandler next) {
+        this.next = next;
+        this.userSessionManagement = userSessionManagement;
+        this.realmConfig = realmConfig;
+    }
+
+    protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        String token = StreamUtil.readString(request.getInputStream());
+        if (token == null) {
+            log.warn("admin request failed, no token");
+            response.sendError(403, "no token");
+            return null;
+        }
+
+        JWSInput input = new JWSInput(token);
+        boolean verified = false;
+        try {
+            verified = RSAProvider.verify(input, realmConfig.getMetadata().getRealmKey());
+        } catch (Exception ignore) {
+        }
+        if (!verified) {
+            log.warn("admin request failed, unable to verify token");
+            response.sendError(403, "verification failed");
+            return null;
+        }
+        return input;
+    }
+
+
+
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        log.debugv("adminActions {0}", exchange.getRequestURI());
+        final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+        HttpServletRequest request = (HttpServletRequest) servletRequestContext.getServletRequest();
+        HttpServletResponse response = (HttpServletResponse) servletRequestContext.getServletResponse();
+        SessionManager manager = servletRequestContext.getDeployment().getSessionManager();
+        String requestUri = exchange.getRequestURI();
+        if (requestUri.endsWith(AdapterAdminResourceConstants.LOGOUT)) {
+            JWSInput token = verifyAdminRequest(request, response);
+            if (token == null) return;
+            userSessionManagement.remoteLogout(token, manager, response);
+            return;
+        } else {
+            next.handleRequest(exchange);
+            return;
+        }
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
index 2bae772..89f8719 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
@@ -3,6 +3,7 @@ package org.keycloak.adapters.undertow;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.api.ConfidentialPortManager;
 import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.SkeletonKeyPrincipal;
 import org.keycloak.adapters.config.RealmConfiguration;
 import org.keycloak.adapters.ResourceMetadata;
 import org.keycloak.SkeletonKeySession;
@@ -17,32 +18,42 @@ import javax.servlet.http.HttpSession;
  */
 public class ServletKeycloakAuthenticationMechanism extends KeycloakAuthenticationMechanism {
     protected ConfidentialPortManager portManager;
+    protected UserSessionManagement userSessionManagement;
 
-    public ServletKeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, AdapterConfig config, RealmConfiguration realmConfig, ConfidentialPortManager portManager) {
-        super(resourceMetadata, config, realmConfig);
+    public ServletKeycloakAuthenticationMechanism(UserSessionManagement userSessionManagement, AdapterConfig config, RealmConfiguration realmConfig, ConfidentialPortManager portManager) {
+        super(config, realmConfig);
         this.portManager = portManager;
+        this.userSessionManagement = userSessionManagement;
     }
 
+    public ServletKeycloakAuthenticationMechanism(AdapterConfig config, ResourceMetadata metadata, ConfidentialPortManager portManager) {
+        super(config, metadata);
+        this.portManager = portManager;
+        this.userSessionManagement = userSessionManagement;
+    }
+
+
     @Override
     protected OAuthAuthenticator createOAuthAuthenticator(HttpServerExchange exchange) {
         return new ServletOAuthAuthenticator(exchange, realmConfig, portManager);
     }
 
     @Override
-    protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession session) {
-        super.propagateBearer(exchange, session);
+    protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession skSession, SkeletonKeyPrincipal principal) {
+        super.propagateBearer(exchange, skSession, principal);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
-        req.setAttribute(SkeletonKeySession.class.getName(), session);
+        req.setAttribute(SkeletonKeySession.class.getName(), skSession);
     }
 
     @Override
-    protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession skSession) {
-        super.propagateOauth(exchange, skSession);
+    protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession skSession, SkeletonKeyPrincipal principal) {
+        super.propagateBearer(exchange, skSession, principal);
         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
         HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
         req.setAttribute(SkeletonKeySession.class.getName(), skSession);
         HttpSession session = req.getSession(true);
         session.setAttribute(SkeletonKeySession.class.getName(), skSession);
+        userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, principal.getName());
     }
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java
new file mode 100755
index 0000000..0cc1b8b
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UserSessionManagement.java
@@ -0,0 +1,188 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticatedSessionManager;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.server.session.SessionListener;
+import io.undertow.server.session.SessionManager;
+import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
+import org.jboss.logging.Logger;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.config.RealmConfiguration;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.representations.adapters.action.LogoutAction;
+import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.StreamUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Manages relationship to users and sessions so that forced admin logout can be implemented
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserSessionManagement implements SessionListener {
+    private static final Logger log = Logger.getLogger(UserSessionManagement.class);
+    private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
+    protected ConcurrentHashMap<String, Set<String>> userSessionMap = new ConcurrentHashMap<String, Set<String>>();
+
+    protected RealmConfiguration realmInfo;
+
+    public UserSessionManagement(RealmConfiguration realmInfo) {
+        this.realmInfo = realmInfo;
+    }
+
+    public void remoteLogout(JWSInput token, SessionManager manager, HttpServletResponse response) throws IOException {
+        try {
+            log.debug("->> remoteLogout: ");
+            LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
+            if (action.isExpired()) {
+                log.warn("admin request failed, expired token");
+                response.sendError(400, "Expired token");
+                return;
+            }
+            if (!realmInfo.getMetadata().getResourceName().equals(action.getResource())) {
+                log.warn("Resource name does not match");
+                response.sendError(400, "Resource name does not match");
+                return;
+
+            }
+            String user = action.getUser();
+            if (user != null) {
+                log.debug("logout of session for: " + user);
+                logout(manager, user);
+            } else {
+                log.debug("logout of all sessions");
+                logoutAll(manager);
+            }
+        } catch (Exception e) {
+            log.warn("failed to logout", e);
+            response.sendError(500, "Failed to logout");
+        }
+        response.setStatus(204);
+    }
+
+    public void login(SessionManager manager, HttpSession session, String username) {
+        String sessionId = session.getId();
+        addAuthenticatedSession(username, sessionId);
+        manager.registerSessionListener(this);
+    }
+
+    protected void addAuthenticatedSession(String username, String sessionId) {
+        synchronized (userSessionMap) {
+            Set<String> map = userSessionMap.get(username);
+            if (map == null) {
+                final Set<String> value = new HashSet<String>();
+                map = userSessionMap.putIfAbsent(username, value);
+                if (map == null) {
+                    map = value;
+                }
+            }
+            synchronized (map) {
+                map.add(sessionId);
+            }
+
+        }
+    }
+
+    protected void removeAuthenticatedSession(String sessionId, String username) {
+        synchronized (userSessionMap) {
+            Set<String> map = userSessionMap.get(username);
+            if (map == null) return;
+            synchronized (map) {
+                map.remove(sessionId);
+                if (map.isEmpty()) userSessionMap.remove(username);
+            }
+        }
+    }
+
+    public void logoutAll(SessionManager manager) {
+        List<String> users = new ArrayList<String>();
+        users.addAll(userSessionMap.keySet());
+        for (String user : users) logout(manager, user);
+    }
+
+    public void logoutAllBut(SessionManager manager, String but) {
+        List<String> users = new ArrayList<String>();
+        users.addAll(userSessionMap.keySet());
+        for (String user : users) {
+            if (!but.equals(user)) logout(manager, user);
+        }
+    }
+
+    public void logout(SessionManager manager, String user) {
+        log.debug("logoutUser: " + user);
+        Set<String> map = userSessionMap.remove(user);
+        if (map == null) {
+            log.debug("no session for user: " + user);
+            return;
+        }
+        log.debug("found session for user");
+        synchronized (map) {
+            for (String id : map) {
+                log.debug("invalidating session for user: " + user);
+                Session session = manager.getSession(id);
+                try {
+                    session.invalidate(null);
+                } catch (Exception e) {
+                    log.warn("Session already invalidated.");
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void sessionCreated(Session session, HttpServerExchange exchange) {
+    }
+
+    @Override
+    public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) {
+        // Look up the single session id associated with this session (if any)
+        String username = getUsernameFromSession(session);
+        if (username == null) return;
+        String sessionId = session.getId();
+        removeAuthenticatedSession(sessionId, username);
+    }
+
+    protected String getUsernameFromSession(Session session) {
+        AuthenticatedSessionManager.AuthenticatedSession authSession = (AuthenticatedSessionManager.AuthenticatedSession) session.getAttribute(AUTH_SESSION_NAME);
+        if (authSession == null) return null;
+        return authSession.getAccount().getPrincipal().getName();
+
+    }
+
+
+    @Override
+    public void sessionIdChanged(Session session, String oldSessionId) {
+        String username = getUsernameFromSession(session);
+        if (username == null) return;
+        removeAuthenticatedSession(oldSessionId, username);
+        addAuthenticatedSession(session.getId(), username);
+    }
+
+    @Override
+    public void attributeAdded(Session session, String name, Object value) {
+    }
+
+    @Override
+    public void attributeUpdated(Session session, String name, Object newValue, Object oldValue) {
+    }
+
+    @Override
+    public void attributeRemoved(Session session, String name, Object oldValue) {
+    }
+
+}
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 2d1c518..82f7ade 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -42,10 +42,8 @@ public class ResourceAdminManager {
         if (managementUrl != null) {
             LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
             String token = new TokenManager().encodeToken(realm, adminAction);
-            Form form = new Form();
-            form.param("token", token);
             logger.debug("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
-            Response response = client.target(managementUrl).path(AdapterAdminResourceConstants.LOGOUT).request().post(Entity.form(form));
+            Response response = client.target(managementUrl).path(AdapterAdminResourceConstants.LOGOUT).request().post(Entity.text(token));
             boolean success = response.getStatus() == 204;
             response.close();
             return success;