keycloak-memoizeit

#dev integrated adapter tomcat7

4/18/2014 3:32:42 AM

Details

diff --git a/integration/pom.xml b/integration/pom.xml
index 366904c..15a2196 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -19,12 +19,12 @@
         <module>servlet-oauth-client</module>
         <module>jboss-adapter-core</module>
         <module>as7-eap6/adapter</module>
+        <module>tomcat7/adapter</module>
         <module>undertow</module>
         <module>wildfly-adapter</module>
         <module>wildfly-subsystem</module>
         <module>as7-eap-subsystem</module>
         <module>js</module>
         <module>installed</module>
-        <!-- <module>as7-eap6/jboss-modules</module> -->
     </modules>
 </project>
diff --git a/integration/tomcat7/adapter/pom.xml b/integration/tomcat7/adapter/pom.xml
new file mode 100755
index 0000000..b3259a6
--- /dev/null
+++ b/integration/tomcat7/adapter/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+<project>
+	<parent>
+		<artifactId>keycloak-parent</artifactId>
+		<groupId>org.keycloak</groupId>
+		<version>1.0-alpha-3</version>
+		<relativePath>../../../pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>keycloak-tomcat7-adapter</artifactId>
+	<name>Keycloak Tomcat7 Integration</name>
+	<description />
+
+	<dependencies>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-adapter-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.keycloak</groupId>
+			<artifactId>keycloak-jboss-adapter-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient</artifactId>
+			<version>${keycloak.apache.httpcomponents.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.iharder</groupId>
+			<artifactId>base64</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcprov-jdk16</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-core-asl</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-mapper-asl</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-xc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.tomcat</groupId>
+			<artifactId>tomcat-servlet-api</artifactId>
+			<version>7.0.52</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.tomcat</groupId>
+			<artifactId>tomcat-catalina</artifactId>
+			<version>7.0.52</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>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java
new file mode 100755
index 0000000..7583061
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/Actions.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.tomcat7;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Actions {
+    public static final String J_OAUTH_ADMIN_FORCED_LOGOUT = "j_oauth_admin_forced_logout";
+    public static final String J_OAUTH_LOGOUT = "j_oauth_logout";
+    public static final String J_OAUTH_RESOLVE_ACCESS_CODE = "j_oauth_resolve_access_code";
+    public static final String J_OAUTH_REMOTE_LOGOUT = "j_oauth_remote_logout";
+    public static final String J_OAUTH_TOKEN_GRANT = "j_oauth_token_grant";
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java
new file mode 100755
index 0000000..9088533
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/AuthenticatedActionsValve.java
@@ -0,0 +1,49 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.management.ObjectName;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Valve;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * Pre-installed actions that must be authenticated
+ * <p/>
+ * Actions include:
+ * <p/>
+ * CORS Origin Check and Response headers
+ * k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
+ *
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class AuthenticatedActionsValve extends ValveBase {
+    private static final Logger log = Logger.getLogger(""+AuthenticatedActionsValve.class);
+    protected KeycloakDeployment deployment;
+
+    public AuthenticatedActionsValve(KeycloakDeployment deployment, Valve next, Container container, ObjectName objectName) {
+        this.deployment = deployment;
+        if (next == null) throw new RuntimeException("WTF is next null?!");
+        setNext(next);
+        setContainer(container);
+    }
+
+
+    @Override
+    public void invoke(Request request, Response response) throws IOException, ServletException {
+        log.finer("AuthenticatedActionsValve.invoke" + request.getRequestURI());
+        AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
+        if (handler.handledRequest()) {
+            return;
+        }
+        getNext().invoke(request, response);
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java
new file mode 100755
index 0000000..8c4618f
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaHttpFacade.java
@@ -0,0 +1,181 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.security.cert.X509Certificate;
+import javax.servlet.http.HttpServletResponse;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaHttpFacade implements HttpFacade {
+    protected org.apache.catalina.connector.Request request;
+    protected HttpServletResponse response;
+    protected RequestFacade requestFacade = new RequestFacade();
+    protected ResponseFacade responseFacade = new ResponseFacade();
+
+    protected class RequestFacade implements Request {
+        @Override
+        public String getURI() {
+            StringBuffer buf = request.getRequestURL();
+            if (request.getQueryString() != null) {
+                buf.append('?').append(request.getQueryString());
+            }
+            return buf.toString();
+        }
+
+        @Override
+        public boolean isSecure() {
+            return request.isSecure();
+        }
+
+        @Override
+        public String getQueryParamValue(String paramName) {
+            return request.getParameter(paramName);
+        }
+
+        @Override
+        public Cookie getCookie(String cookieName) {
+            if (request.getCookies() == null) return null;
+            javax.servlet.http.Cookie cookie = null;
+            for (javax.servlet.http.Cookie c : request.getCookies()) {
+                if (c.getName().equals(cookieName)) {
+                    cookie = c;
+                    break;
+                }
+            }
+            if (cookie == null) return null;
+            return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
+        }
+
+        @Override
+        public List<String> getHeaders(String name) {
+            Enumeration<String> headers = request.getHeaders(name);
+            if (headers == null) return null;
+            List<String> list = new ArrayList<String>();
+            while (headers.hasMoreElements()) {
+                list.add(headers.nextElement());
+            }
+            return list;
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            try {
+                return request.getInputStream();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public String getMethod() {
+            return request.getMethod();
+        }
+
+        @Override
+        public String getHeader(String name) {
+            return request.getHeader(name);
+        }
+    }
+
+    protected class ResponseFacade implements Response {
+        protected boolean ended;
+
+        @Override
+        public void setStatus(int status) {
+            response.setStatus(status);
+        }
+
+        @Override
+        public void addHeader(String name, String value) {
+            response.addHeader(name, value);
+        }
+
+        @Override
+        public void setHeader(String name, String value) {
+            response.setHeader(name, value);
+        }
+
+        @Override
+        public void resetCookie(String name, String path) {
+            setCookie(name, "", null, path, 0, false, false);
+        }
+
+        @Override
+        public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+            javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value);
+            if (domain != null) cookie.setDomain(domain);
+            if (path != null) cookie.setPath(path);
+            if (secure) cookie.setSecure(true);
+            if (httpOnly) cookie.setHttpOnly(httpOnly);
+            cookie.setMaxAge(maxAge);
+            response.addCookie(cookie);
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            try {
+                return response.getOutputStream();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void sendError(int code, String message) {
+            try {
+                response.sendError(code, message);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void end() {
+            ended = true;
+        }
+
+        public boolean isEnded() {
+            return ended;
+        }
+    }
+
+    public CatalinaHttpFacade(org.apache.catalina.connector.Request request, HttpServletResponse response) {
+        this.request = request;
+        this.response = response;
+    }
+
+    @Override
+    public Request getRequest() {
+        return requestFacade;
+    }
+
+    @Override
+    public Response getResponse() {
+        return responseFacade;
+    }
+
+    @Override
+    public KeycloakSecurityContext getSecurityContext() {
+        return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+    }
+
+    @Override
+    public X509Certificate[] getCertificateChain() {
+        throw new IllegalStateException("Not supported yet");
+    }
+
+    public boolean isEnded() {
+        return responseFacade.isEnded();
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
new file mode 100755
index 0000000..7ed1f1d
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -0,0 +1,123 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.representations.AccessToken;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaRequestAuthenticator extends RequestAuthenticator {
+    private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class);
+    protected KeycloakAuthenticatorValve valve;
+    protected CatalinaUserSessionManagement userSessionManagement;
+    protected Request request;
+
+    public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
+                                        KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
+                                        CatalinaHttpFacade facade,
+                                        Request request) {
+        super(facade, deployment, request.getConnector().getRedirectPort());
+        this.valve = valve;
+        this.userSessionManagement = userSessionManagement;
+        this.request = request;
+    }
+
+    @Override
+    protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+        return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
+            @Override
+            protected void saveRequest() {
+                try {
+                    valve.keycloakSaveRequest(request);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+    }
+
+    @Override
+    protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) {
+        Set<String> roles = getRolesFromToken(securityContext);
+        GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
+        Session session = request.getSessionInternal(true);
+        session.setPrincipal(principal);
+        session.setAuthType("OAUTH");
+        session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+        String username = securityContext.getToken().getSubject();
+        log.finer("userSessionManage.login: " + username);
+        userSessionManagement.login(session, username);
+    }
+
+    @Override
+    protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) {
+        Set<String> roles = getRolesFromToken(securityContext);
+        for (String role : roles) {
+            log.info("Bearer role: " + role);
+        }
+        Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
+        request.setUserPrincipal(generalPrincipal);
+        request.setAuthType("KEYCLOAK");
+        request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+    }
+
+    protected Set<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
+        Set<String> roles = null;
+        if (deployment.isUseResourceRoleMappings()) {
+            AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
+            if (access != null) roles = access.getRoles();
+        } else {
+            AccessToken.Access access =  session.getToken().getRealmAccess();
+            if (access != null) roles = access.getRoles();
+        }
+        if (roles == null) roles = Collections.emptySet();
+        return roles;
+    }
+
+    @Override
+    protected boolean isCached() {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+            return false;
+        log.finer("remote logged in already");
+        GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+        request.setUserPrincipal(principal);
+        request.setAuthType("KEYCLOAK");
+        Session session = request.getSessionInternal();
+        if (session != null) {
+            RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
+            if (securityContext != null) {
+                securityContext.setDeployment(deployment);
+                request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+            }
+        }
+        restoreRequest();
+        return true;
+    }
+
+    protected void restoreRequest() {
+        if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
+            if (valve.keycloakRestoreRequest(request)) {
+                log.finer("restoreRequest");
+            } else {
+                log.finer("Restore of original request failed");
+                throw new RuntimeException("Restore of original request failed");
+            }
+        }
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java
new file mode 100755
index 0000000..b4e9a59
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSecurityContextHelper.java
@@ -0,0 +1,131 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaSecurityContextHelper {
+    public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet, final KeycloakSecurityContext securityContext) {
+//        KeycloakAccount account = new KeycloakAccount() {
+//            @Override
+//            public Principal getPrincipal() {
+//                return identity;
+//            }
+//
+//            @Override
+//            public Set<String> getRoles() {
+//                return roleSet;
+//            }
+//
+//            @Override
+//            public KeycloakSecurityContext getKeycloakSecurityContext() {
+//                return securityContext;
+//            }
+//        };
+        Subject subject = new Subject();
+        Set<Principal> principals = subject.getPrincipals();
+        principals.add(identity);
+        Group[] roleSets = getRoleSets(roleSet);
+        for (int g = 0; g < roleSets.length; g++) {
+            Group group = roleSets[g];
+            String name = group.getName();
+            Group subjectGroup = createGroup(name, principals);
+//            if (subjectGroup instanceof NestableGroup) {
+//                /* A NestableGroup only allows Groups to be added to it so we
+//                need to add a SimpleGroup to subjectRoles to contain the roles
+//                */
+//                SimpleGroup tmp = new SimpleGroup("Roles");
+//                subjectGroup.addMember(tmp);
+//                subjectGroup = tmp;
+//            }
+            // Copy the group members to the Subject group
+            Enumeration<? extends Principal> members = group.members();
+            while (members.hasMoreElements()) {
+                Principal role = (Principal) members.nextElement();
+                subjectGroup.addMember(role);
+            }
+        }
+        Principal userPrincipal = getPrincipal(subject);
+        List<String> rolesAsStringList = new ArrayList<String>();
+        rolesAsStringList.addAll(roleSet);
+        GenericPrincipal principal = new GenericPrincipal(userPrincipal.getName(), null, rolesAsStringList, userPrincipal, null);
+        return principal;
+    }
+
+    /**
+     * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
+     * considered or the single subject inside the CallerPrincipal group.
+     *
+     * @param subject
+     * @return the authenticated subject
+     */
+    protected Principal getPrincipal(Subject subject) {
+        Principal principal = null;
+        Principal callerPrincipal = null;
+        if (subject != null) {
+            Set<Principal> principals = subject.getPrincipals();
+            if (principals != null && !principals.isEmpty()) {
+                for (Principal p : principals) {
+                    if (!(p instanceof Group) && principal == null) {
+                        principal = p;
+                    }
+//                    if (p instanceof Group) {
+//                        Group g = Group.class.cast(p);
+//                        if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+//                            Enumeration<? extends Principal> e = g.members();
+//                            if (e.hasMoreElements())
+//                                callerPrincipal = e.nextElement();
+//                        }
+//                    }
+                }
+            }
+        }
+        return callerPrincipal == null ? principal : callerPrincipal;
+    }
+
+    protected Group createGroup(String name, Set<Principal> principals) {
+        Group roles = null;
+        Iterator<Principal> iter = principals.iterator();
+        while (iter.hasNext()) {
+            Object next = iter.next();
+            if ((next instanceof Group) == false)
+                continue;
+            Group grp = (Group) next;
+            if (grp.getName().equals(name)) {
+                roles = grp;
+                break;
+            }
+        }
+        // If we did not find a group create one
+        if (roles == null) {
+            roles = new SimpleGroup(name);
+            principals.add(roles);
+        }
+        return roles;
+    }
+
+    protected Group[] getRoleSets(Collection<String> roleSet) {
+        SimpleGroup roles = new SimpleGroup("Roles");
+        Group[] roleSets = {roles};
+        for (String role : roleSet) {
+            roles.addMember(new SimplePrincipal(role));
+        }
+        return roleSets;
+    }
+
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
new file mode 100755
index 0000000..a84284e
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
@@ -0,0 +1,137 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.adapters.UserSessionManagement;
+
+/**
+ * Manages relationship to users and sessions so that forced admin logout can be implemented
+ *
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
+    private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
+    protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
+
+    public static class UserSessions {
+        protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
+        protected long loggedIn = System.currentTimeMillis();
+
+
+        public Map<String, Session> getSessions() {
+            return sessions;
+        }
+
+        public long getLoggedIn() {
+            return loggedIn;
+        }
+    }
+
+    @Override
+    public int getActiveSessions() {
+        int active = 0;
+        synchronized (userSessionMap) {
+            for (UserSessions sessions : userSessionMap.values()) {
+                active += sessions.getSessions().size();
+            }
+
+        }
+        return active;
+    }
+
+    /**
+     *
+     * @param username
+     * @return null if user not logged in
+     */
+    @Override
+    public Long getUserLoginTime(String username) {
+        UserSessions sessions = userSessionMap.get(username);
+        if (sessions == null) return null;
+        return sessions.getLoggedIn();
+    }
+
+    @Override
+    public Set<String> getActiveUsers() {
+        HashSet<String> set = new HashSet<String>();
+        set.addAll(userSessionMap.keySet());
+        return set;
+    }
+
+    protected void login(Session session, String username) {
+        synchronized (userSessionMap) {
+            UserSessions userSessions = userSessionMap.get(username);
+            if (userSessions == null) {
+                userSessions = new UserSessions();
+                userSessionMap.put(username, userSessions);
+            }
+            userSessions.getSessions().put(session.getId(), session);
+        }
+        session.addSessionListener(this);
+    }
+
+    @Override
+    public void logoutAll() {
+        List<String> users = new ArrayList<String>();
+        users.addAll(userSessionMap.keySet());
+        for (String user : users) logout(user);
+    }
+
+    @Override
+    public void logout(String user) {
+        log.finer("logoutUser: " + user);
+        UserSessions sessions = null;
+        synchronized (userSessionMap) {
+            sessions = userSessionMap.remove(user);
+
+        }
+        if (sessions == null) {
+            log.finer("no session for user: " + user);
+            return;
+
+        }
+        
+        log.finer("found session for user");
+        for (Session session : sessions.getSessions().values()) {
+            session.setPrincipal(null);
+            session.setAuthType(null);
+            session.getSession().invalidate();
+        }
+    }
+
+    public void sessionEvent(SessionEvent event) {
+        // We only care about session destroyed events
+        if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
+                && (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType())))
+            return;
+
+        // Look up the single session id associated with this session (if any)
+        Session session = event.getSession();
+        GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+        if (principal == null) return;
+        session.setPrincipal(null);
+        session.setAuthType(null);
+
+        String username = principal.getUserPrincipal().getName();
+        synchronized (userSessionMap) {
+            UserSessions sessions = userSessionMap.get(username);
+            if (sessions != null) {
+                sessions.getSessions().remove(session.getId());
+                if (sessions.getSessions().isEmpty()) {
+                    userSessionMap.remove(username);
+                }
+            }
+        }
+    }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java
new file mode 100755
index 0000000..d614872
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CorsPreflightChecker.java
@@ -0,0 +1,59 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CorsPreflightChecker {
+    private static final Logger log = Logger.getLogger(""+CorsPreflightChecker.class);
+    protected KeycloakDeployment deployment;
+
+    public CorsPreflightChecker(KeycloakDeployment deployment) {
+        this.deployment = deployment;
+    }
+
+    public boolean checkCorsPreflight(Request request, Response response) {
+        log.finer("checkCorsPreflight " + request.getRequestURI());
+        if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
+            log.finer("checkCorsPreflight: not options ");
+            return false;
+
+        }
+        if (request.getHeader("Origin") == null) {
+            log.finer("checkCorsPreflight: no origin header");
+            return false;
+        }
+        log.finer("Preflight request returning");
+        response.setStatus(HttpServletResponse.SC_OK);
+        String origin = request.getHeader("Origin");
+        response.setHeader("Access-Control-Allow-Origin", origin);
+        response.setHeader("Access-Control-Allow-Credentials", "true");
+        String requestMethods = request.getHeader("Access-Control-Request-Method");
+        if (requestMethods != null) {
+            if (deployment.getCorsAllowedMethods() != null) {
+                requestMethods = deployment.getCorsAllowedMethods();
+            }
+            response.setHeader("Access-Control-Allow-Methods", requestMethods);
+        }
+        String allowHeaders = request.getHeader("Access-Control-Request-Headers");
+        if (allowHeaders != null) {
+            if (deployment.getCorsAllowedHeaders() != null) {
+                allowHeaders = deployment.getCorsAllowedHeaders();
+            }
+            response.setHeader("Access-Control-Allow-Headers", allowHeaders);
+        }
+        if (deployment.getCorsMaxAge() > -1) {
+            response.setHeader("Access-Control-Max-Age", Integer.toString(deployment.getCorsMaxAge()));
+        }
+        return true;
+    }
+
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
new file mode 100755
index 0000000..2dd4e97
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -0,0 +1,178 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+/**
+ * Web deployment whose security is managed by a remote OAuth Skeleton Key
+ * authentication server
+ * <p/>
+ * Redirects browser to remote authentication server if not logged in. Also
+ * allows OAuth Bearer Token requests that contain a Skeleton Key bearer tokens.
+ * 
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+	private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class);
+	protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
+    protected KeycloakDeployment deployment;
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        if (event.getType() == Lifecycle.START_EVENT) {
+            try {
+                startDeployment();
+            } catch (LifecycleException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void startDeployment() throws LifecycleException {
+        super.start();
+        StandardContext standardContext = (StandardContext) context;
+        standardContext.addLifecycleListener(this);
+        cache = false;
+    }
+
+    public void initInternal() {
+        this.deployment = KeycloakDeploymentBuilder.build(getConfigInputStream(context));
+        log.info("deployment realm:" + deployment.getRealm() + " resource:" + deployment.getResourceName());
+        AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deployment, getNext(), getContainer(), getObjectName());
+        setNext(actions);
+    }
+
+    private static InputStream getJSONFromServletContext(ServletContext servletContext) {
+        String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
+        if (json == null) {
+            return null;
+        }
+        log.info("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME);
+        log.info(json);
+        return new ByteArrayInputStream(json.getBytes());
+    }
+
+    private static InputStream getConfigInputStream(Context context) {
+        InputStream is = getJSONFromServletContext(context.getServletContext());
+        if (is == null) {
+            String path = context.getServletContext().getInitParameter("keycloak.config.file");
+            if (path == null) {
+                log.info("**** using /WEB-INF/keycloak.json");
+                is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json");
+            } else {
+                try {
+                    is = new FileInputStream(path);
+                } catch (FileNotFoundException e) {
+                	log.severe("NOT FOUND /WEB-INF/keycloak.json");
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return is;
+    }
+
+    @Override
+    public void invoke(Request request, Response response) throws IOException, ServletException {
+        try {
+            PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment,
+                    new CatalinaHttpFacade(request, response));
+            if (handler.handleRequest()) {
+                return;
+            }
+            checkKeycloakSession(request);
+            super.invoke(request, response);
+        } finally {
+        }
+    }
+
+    @Override
+    public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
+        CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
+        CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
+        AuthOutcome outcome = authenticator.authenticate();
+        if (outcome == AuthOutcome.AUTHENTICATED) {
+            if (facade.isEnded()) {
+                return false;
+            }
+            return true;
+        }
+        AuthChallenge challenge = authenticator.getChallenge();
+        if (challenge != null) {
+            challenge.challenge(facade);
+        }
+        return false;
+    }
+
+    /**
+     * Checks that access token is still valid. Will attempt refresh of token if
+     * it is not.
+     * 
+     * @param request
+     */
+    protected void checkKeycloakSession(Request request) {
+        if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+            return;
+        RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal()
+                .getNote(KeycloakSecurityContext.class.getName());
+        if (session == null)
+            return;
+        // just in case session got serialized
+        session.setDeployment(deployment);
+        if (session.isActive())
+            return;
+
+        // FYI: A refresh requires same scope, so same roles will be set.
+        // Otherwise, refresh will fail and token will
+        // not be updated
+        session.refreshExpiredToken();
+        if (session.isActive())
+            return;
+
+        request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
+        request.setUserPrincipal(null);
+        request.setAuthType(null);
+        request.getSessionInternal().setPrincipal(null);
+        request.getSessionInternal().setAuthType(null);
+    }
+
+    public void keycloakSaveRequest(Request request) throws IOException {
+        saveRequest(request, request.getSessionInternal(true));
+    }
+
+    public boolean keycloakRestoreRequest(Request request) {
+        try {
+            return restoreRequest(request, request.getSessionInternal());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java
new file mode 100644
index 0000000..b583d3b
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimpleGroup.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SimpleGroup extends SimplePrincipal implements Group {
+    private static final long serialVersionUID = 3273437693505893786L;
+    private final Set<Principal> members = new HashSet<Principal>();
+
+    /**
+     * Creates a new group with the given name.
+     * @param name Group name.
+     */
+    public SimpleGroup(final String name) {
+        super(name);
+    }
+
+    public boolean addMember(final Principal user) {
+        return this.members.add(user);
+    }
+
+    public boolean isMember(final Principal member) {
+        return this.members.contains(member);
+    }
+
+    public Enumeration<? extends Principal> members() {
+        return Collections.enumeration(this.members);
+    }
+
+    public boolean removeMember(final Principal user) {
+        return this.members.remove(user);
+    }
+    
+    public String toString() {
+        return super.toString() + ": " + members.toString();
+    }
+
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java
new file mode 100644
index 0000000..e3d6507
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/SimplePrincipal.java
@@ -0,0 +1,51 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+/**
+ * Simple security principal implementation.
+ *
+ * @author Marvin S. Addison
+ * @version $Revision: 22071 $
+ * @since 3.1.11
+ *
+ */
+public class SimplePrincipal implements Principal, Serializable {
+
+    /** SimplePrincipal.java */
+    private static final long serialVersionUID = -5645357206342793145L;
+
+    /** The unique identifier for this principal. */
+    private final String name;
+
+    /**
+     * Creates a new principal with the given name.
+     * @param name Principal name.
+     */
+    public SimplePrincipal(final String name) {
+        this.name = name;
+    }
+
+    public final String getName() {
+        return this.name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public boolean equals(final Object o) {
+        if (o == null) {
+            return false;
+        } else if (!(o instanceof SimplePrincipal)) {
+            return false;
+        } else {
+            return getName().equals(((SimplePrincipal)o).getName());
+        }
+    }
+
+    public int hashCode() {
+        return 37 * getName().hashCode();
+    }
+}
\ No newline at end of file