From 3805510e2092e3d5ac4551fee7c77b2733ebf989 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 7 Nov 2014 18:34:53 -0500 Subject: [PATCH] more jetty adapter --- distribution/jetty9-adapter-zip/assembly.xml | 33 ++++ distribution/jetty9-adapter-zip/keycloak.mod | 11 ++ distribution/jetty9-adapter-zip/pom.xml | 53 ++++++ integration/jetty9/adapter/pom.xml | 2 +- .../adapters/jetty/JettyCookieTokenStore.java | 3 - .../jetty/JettyRequestAuthenticator.java | 8 +- .../jetty/JettySessionTokenStore.java | 1 - .../jetty/KeycloakJettyAuthenticator.java | 169 +++++++++++------- testsuite/jetty9/pom.xml | 2 +- .../org/keycloak/testsuite/Jetty9Test.java | 22 +++ 10 files changed, 232 insertions(+), 72 deletions(-) create mode 100755 distribution/jetty9-adapter-zip/assembly.xml create mode 100755 distribution/jetty9-adapter-zip/keycloak.mod create mode 100755 distribution/jetty9-adapter-zip/pom.xml diff --git a/distribution/jetty9-adapter-zip/assembly.xml b/distribution/jetty9-adapter-zip/assembly.xml new file mode 100755 index 0000000000..ab8c4bb69c --- /dev/null +++ b/distribution/jetty9-adapter-zip/assembly.xml @@ -0,0 +1,33 @@ + + war-dist + + + zip + + false + + + + + + keycloak.mod + + modules + + + ${project.build.directory}/modules + + + + + + false + true + true + + org.keycloak:keycloak-jetty9-adapter + + lib/keycloak + + + diff --git a/distribution/jetty9-adapter-zip/keycloak.mod b/distribution/jetty9-adapter-zip/keycloak.mod new file mode 100755 index 0000000000..130f4e9efa --- /dev/null +++ b/distribution/jetty9-adapter-zip/keycloak.mod @@ -0,0 +1,11 @@ +# +# Keycloak Jetty Adapter +# + +[depend] +server +security + +[lib] +lib/keycloak/*.jar + diff --git a/distribution/jetty9-adapter-zip/pom.xml b/distribution/jetty9-adapter-zip/pom.xml new file mode 100755 index 0000000000..344c5af362 --- /dev/null +++ b/distribution/jetty9-adapter-zip/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + + keycloak-parent + org.keycloak + 1.1.0.Beta2-SNAPSHOT + ../../pom.xml + + + keycloak-jetty9-adapter-dist + pom + Keycloak Jetty 9 Adapter Distro + + + + + org.keycloak + keycloak-jetty9-adapter + ${project.version} + + + + + + maven-assembly-plugin + 2.4 + + + assemble + package + + single + + + + assembly.xml + + + target + + + target/assembly/work + + false + + + + + + + + diff --git a/integration/jetty9/adapter/pom.xml b/integration/jetty9/adapter/pom.xml index ba8ac3b7ae..7550ad4692 100755 --- a/integration/jetty9/adapter/pom.xml +++ b/integration/jetty9/adapter/pom.xml @@ -12,7 +12,7 @@ keycloak-jetty9-adapter Keycloak Jetty 9 Integration - 9.1.0.v20131115 + 9.2.4.v20141103 diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java index aa685b33a8..c0b1685e3d 100755 --- a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyCookieTokenStore.java @@ -5,7 +5,6 @@ import org.jboss.logging.Logger; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.AdapterTokenStore; -import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.CookieTokenStore; import org.keycloak.adapters.HttpFacade; import org.keycloak.adapters.KeycloakAccount; @@ -13,8 +12,6 @@ import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RequestAuthenticator; -import java.util.Set; - /** * Handle storage of token info in cookie. Per-request object. * diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java index 64ce3a6503..c4158b6c7e 100755 --- a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettyRequestAuthenticator.java @@ -20,7 +20,6 @@ import org.keycloak.enums.TokenStore; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import java.io.IOException; import java.security.Principal; import java.util.Set; @@ -70,8 +69,9 @@ public class JettyRequestAuthenticator extends RequestAuthenticator { if (MimeTypes.Type.FORM_ENCODED.is(request.getContentType()) && HttpMethod.POST.is(request.getMethod())) { Request base_request = (request instanceof Request) ? (Request) request : HttpChannel .getCurrentHttpChannel().getRequest(); - base_request.extractParameters(); - session.setAttribute(FormAuthenticator.__J_POST, new MultiMap(base_request.getParameters())); + MultiMap formParameters = new MultiMap(); + base_request.extractFormParameters(formParameters); + session.setAttribute(FormAuthenticator.__J_POST, formParameters); } } } @@ -134,7 +134,7 @@ public class JettyRequestAuthenticator extends RequestAuthenticator { MultiMap j_post = (MultiMap) session.getAttribute(FormAuthenticator.__J_POST); if (j_post != null) { Request base_request = HttpChannel.getCurrentHttpChannel().getRequest(); - base_request.setParameters(j_post); + base_request.setContentParameters(j_post); } session.removeAttribute(FormAuthenticator.__J_URI); session.removeAttribute(FormAuthenticator.__J_METHOD); diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java index 8859e22b69..f7b16a3af4 100755 --- a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/JettySessionTokenStore.java @@ -12,7 +12,6 @@ import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RequestAuthenticator; import javax.servlet.http.HttpSession; -import java.util.Set; /** * Handle storage of token info in HTTP Session. Per-request object diff --git a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java index 9f29340412..618f431b59 100755 --- a/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java +++ b/integration/jetty9/adapter/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java @@ -1,20 +1,18 @@ package org.keycloak.adapters.jetty; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.DefaultUserIdentity; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; -import org.eclipse.jetty.security.authentication.FormAuthenticator; +import org.eclipse.jetty.security.authentication.DeferredAuthentication; +import org.eclipse.jetty.security.authentication.LoginAuthenticator; import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.MultiMap; import org.jboss.logging.Logger; import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.AdapterConstants; import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterTokenStore; @@ -30,89 +28,97 @@ import org.keycloak.adapters.NodesRegistrationManagement; import org.keycloak.adapters.PreAuthActionsHandler; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.enums.TokenStore; +import org.keycloak.representations.adapters.config.AdapterConfig; import javax.security.auth.Subject; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; -import java.security.Principal; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Set; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class KeycloakJettyAuthenticator extends FormAuthenticator { +public class KeycloakJettyAuthenticator extends LoginAuthenticator { private static final org.jboss.logging.Logger log = Logger.getLogger(KeycloakJettyAuthenticator.class); protected AdapterDeploymentContext deploymentContext; protected NodesRegistrationManagement nodesRegistrationManagement; + protected AdapterConfig adapterConfig; + protected KeycloakConfigResolver configResolver; public KeycloakJettyAuthenticator() { super(); } - public KeycloakJettyAuthenticator(String login, String error, boolean dispatch) { - super(login, error, dispatch); + @Override + public void setConfiguration(AuthConfiguration configuration) { + //super.setConfiguration(configuration); + initializeKeycloak(); } @Override - public void setConfiguration(AuthConfiguration configuration) { - super.setConfiguration(configuration); - initializeKeycloak(); + public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException + { + return true; + } + + public AdapterConfig getAdapterConfig() { + return adapterConfig; + } + + public void setAdapterConfig(AdapterConfig adapterConfig) { + this.adapterConfig = adapterConfig; + } + + public KeycloakConfigResolver getConfigResolver() { + return configResolver; + } + + public void setConfigResolver(KeycloakConfigResolver configResolver) { + this.configResolver = configResolver; } @SuppressWarnings("UseSpecificCatch") public void initializeKeycloak() { + nodesRegistrationManagement = new NodesRegistrationManagement(); String contextPath = ContextHandler.getCurrentContext().getContextPath(); ServletContext theServletContext = ContextHandler.getCurrentContext().getContext(contextPath); - // Possible scenarios: - // 1) The deployment has a keycloak.config.resolver specified and it exists: - // Outcome: adapter uses the resolver - // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) : - // Outcome: adapter is left unconfigured - // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent) - // Outcome: adapter uses it - // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent) - // Outcome: adapter is left unconfigured - String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver"); - if (configResolverClass != null) { - try { - KeycloakConfigResolver configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance(); - deploymentContext = new AdapterDeploymentContext(configResolver); - log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass); - } catch (Exception ex) { - log.infov("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()}); - deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); + if (configResolver == null) { + String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver"); + if (configResolverClass != null) { + try { + configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance(); + log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass); + } catch (Exception ex) { + log.infov("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()}); + } } - } else { - InputStream configInputStream = getConfigInputStream(theServletContext); - KeycloakDeployment kd; - if (configInputStream == null) { - log.debug("No adapter configuration. Keycloak is unconfigured and will deny all requests."); - kd = new KeycloakDeployment(); - } else { - kd = KeycloakDeploymentBuilder.build(configInputStream); - } - deploymentContext = new AdapterDeploymentContext(kd); - log.debug("Keycloak is using a per-deployment configuration."); } - theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); - //AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer()); - //setNext(actions); + if (configResolver != null) { + deploymentContext = new AdapterDeploymentContext(configResolver); + } else if (adapterConfig != null) { + KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig); + deploymentContext = new AdapterDeploymentContext(kd); + } else { + InputStream configInputStream = getConfigInputStream(theServletContext); + if (configInputStream == null) { + deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); - nodesRegistrationManagement = new NodesRegistrationManagement(); + } else { + deploymentContext = new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(configInputStream)); + } + + } + theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); } private static InputStream getJSONFromServletContext(ServletContext servletContext) { @@ -146,6 +152,8 @@ public class KeycloakJettyAuthenticator extends FormAuthenticator { if (log.isTraceEnabled()) { log.trace("*** authenticate"); } + if (!mandatory) + return new DeferredAuthentication(this); Request request = HttpChannel.getCurrentHttpChannel().getRequest(); JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse)res); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); @@ -189,7 +197,7 @@ public class KeycloakJettyAuthenticator extends FormAuthenticator { } public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; - protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) { + public static AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) { AdapterTokenStore store = (AdapterTokenStore)request.getAttribute(TOKEN_STORE_NOTE); if (store != null) { return store; @@ -205,24 +213,61 @@ public class KeycloakJettyAuthenticator extends FormAuthenticator { return store; } - protected Authentication register(HttpServletRequest httpServletRequest, KeycloakPrincipal principal) { - Set roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext()); - if (roles == null) { - roles = new HashSet(); + public static class KeycloakAuthentication extends UserAuthentication + { + public KeycloakAuthentication(String method, UserIdentity userIdentity) { + super(method, userIdentity); } - Request request = (Request) httpServletRequest; - Authentication authentication = request.getAuthentication(); - if (!(authentication instanceof UserAuthentication)) { - Subject theSubject = new Subject(); - String[] theRoles = new String[roles.size()]; - roles.toArray(theRoles); - UserIdentity userIdentity = new DefaultUserIdentity(theSubject, principal, theRoles); - authentication = new UserAuthentication(getAuthMethod(), userIdentity); + @Override + public void logout() { + Request request = HttpChannel.getCurrentHttpChannel().getRequest(); + logoutCurrent(request); + } + + + + } + + public static void logoutCurrent(Request request) { + AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext)request.getAttribute(AdapterDeploymentContext.class.getName()); + KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName()); + if (ksc != null) { + JettyHttpFacade facade = new JettyHttpFacade(request, null); + KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); + if (ksc instanceof RefreshableKeycloakSecurityContext) { + ((RefreshableKeycloakSecurityContext) ksc).logout(deployment); + } + + AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment); + tokenStore.logout(); + request.removeAttribute(KeycloakSecurityContext.class.getName()); + } + } + + + protected Authentication register(Request request, KeycloakPrincipal principal) { + request.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); + Authentication authentication = request.getAuthentication(); + if (!(authentication instanceof KeycloakAuthentication)) { + UserIdentity userIdentity = createIdentity(principal); + authentication = new KeycloakAuthentication(getAuthMethod(), userIdentity); request.setAuthentication(authentication); } return authentication; } + public static UserIdentity createIdentity(KeycloakPrincipal principal) { + Set roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext()); + if (roles == null) { + roles = new HashSet(); + } + Subject theSubject = new Subject(); + String[] theRoles = new String[roles.size()]; + roles.toArray(theRoles); + + return new DefaultUserIdentity(theSubject, principal, theRoles); + } + } diff --git a/testsuite/jetty9/pom.xml b/testsuite/jetty9/pom.xml index fd72e85d9c..85ba650483 100755 --- a/testsuite/jetty9/pom.xml +++ b/testsuite/jetty9/pom.xml @@ -12,7 +12,7 @@ keycloak-testsuite-jetty9 Keycloak Jetty 9 Integration TestSuite - 9.1.0.v20131115 + 9.2.4.v20141103 diff --git a/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java b/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java index abb019cd67..115a9e4914 100755 --- a/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java +++ b/testsuite/jetty9/src/test/java/org/keycloak/testsuite/Jetty9Test.java @@ -84,6 +84,14 @@ public class Jetty9Test { public static class SendUsernameServlet extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + if (req.getPathInfo().endsWith("logout")) { + req.logout(); + resp.setContentType("text/plain"); + OutputStream stream = resp.getOutputStream(); + stream.write("logout".getBytes()); + return; + + } resp.setContentType("text/plain"); OutputStream stream = resp.getOutputStream(); Principal principal = req.getUserPrincipal(); @@ -199,6 +207,20 @@ public class Jetty9Test { String currentUrl = driver.getCurrentUrl(); Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); + // test servletRequest.logout() + loginPage.login("bburke@redhat.com", "password"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/"); + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("Bill Burke")); + driver.navigate().to("http://localhost:8080/customer-portal/logout"); + pageSource = driver.getPageSource(); + Assert.assertTrue(pageSource.contains("logout")); + driver.navigate().to("http://localhost:8080/customer-portal"); + currentUrl = driver.getCurrentUrl(); + Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); + }