more jetty adapter
This commit is contained in:
parent
31050e0580
commit
3805510e20
10 changed files with 232 additions and 72 deletions
33
distribution/jetty9-adapter-zip/assembly.xml
Executable file
33
distribution/jetty9-adapter-zip/assembly.xml
Executable file
|
@ -0,0 +1,33 @@
|
|||
<assembly>
|
||||
<id>war-dist</id>
|
||||
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory></directory>
|
||||
<includes>
|
||||
<include>keycloak.mod</include>
|
||||
</includes>
|
||||
<outputDirectory>modules</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/modules</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<unpack>false</unpack>
|
||||
<useTransitiveDependencies>true</useTransitiveDependencies>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
<includes>
|
||||
<include>org.keycloak:keycloak-jetty9-adapter</include>
|
||||
</includes>
|
||||
<outputDirectory>lib/keycloak</outputDirectory>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
11
distribution/jetty9-adapter-zip/keycloak.mod
Executable file
11
distribution/jetty9-adapter-zip/keycloak.mod
Executable file
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# Keycloak Jetty Adapter
|
||||
#
|
||||
|
||||
[depend]
|
||||
server
|
||||
security
|
||||
|
||||
[lib]
|
||||
lib/keycloak/*.jar
|
||||
|
53
distribution/jetty9-adapter-zip/pom.xml
Executable file
53
distribution/jetty9-adapter-zip/pom.xml
Executable file
|
@ -0,0 +1,53 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0.Beta2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>keycloak-jetty9-adapter-dist</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Keycloak Jetty 9 Adapter Distro</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty9-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>
|
||||
target
|
||||
</outputDirectory>
|
||||
<workDirectory>
|
||||
target/assembly/work
|
||||
</workDirectory>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -12,7 +12,7 @@
|
|||
<artifactId>keycloak-jetty9-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9 Integration</name>
|
||||
<properties>
|
||||
<jetty9.version>9.1.0.v20131115</jetty9.version>
|
||||
<jetty9.version>9.2.4.v20141103</jetty9.version>
|
||||
</properties>
|
||||
<description />
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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<String>(base_request.getParameters()));
|
||||
MultiMap<String> formParameters = new MultiMap<String>();
|
||||
base_request.extractFormParameters(formParameters);
|
||||
session.setAttribute(FormAuthenticator.__J_POST, formParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ public class JettyRequestAuthenticator extends RequestAuthenticator {
|
|||
MultiMap<String> j_post = (MultiMap<String>) 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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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
|
||||
|
||||
if (configResolver == null) {
|
||||
String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver");
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
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()});
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
KeycloakDeployment kd;
|
||||
if (configInputStream == null) {
|
||||
log.debug("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||
kd = new KeycloakDeployment();
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
|
||||
} else {
|
||||
kd = KeycloakDeploymentBuilder.build(configInputStream);
|
||||
}
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.debug("Keycloak is using a per-deployment configuration.");
|
||||
deploymentContext = new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(configInputStream));
|
||||
}
|
||||
|
||||
}
|
||||
theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
//AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer());
|
||||
//setNext(actions);
|
||||
|
||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
}
|
||||
|
||||
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<RefreshableKeycloakSecurityContext> principal) {
|
||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext());
|
||||
if (roles == null) {
|
||||
roles = new HashSet<String>();
|
||||
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<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext> principal) {
|
||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext());
|
||||
if (roles == null) {
|
||||
roles = new HashSet<String>();
|
||||
}
|
||||
Subject theSubject = new Subject();
|
||||
String[] theRoles = new String[roles.size()];
|
||||
roles.toArray(theRoles);
|
||||
|
||||
return new DefaultUserIdentity(theSubject, principal, theRoles);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<artifactId>keycloak-testsuite-jetty9</artifactId>
|
||||
<name>Keycloak Jetty 9 Integration TestSuite</name>
|
||||
<properties>
|
||||
<jetty9.version>9.1.0.v20131115</jetty9.version>
|
||||
<jetty9.version>9.2.4.v20141103</jetty9.version>
|
||||
</properties>
|
||||
<description />
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue