KEYCLOAK-3300 Add support for jetty in spring-boot-adapter
This adds support for using Jetty together with the Keycloak spring-boot-adapter. Sadly the KeycloakSpringBootProperties.SecurityCollection definition is mostly inspired by Tomcats SecurityConstraint/SecurityCollection which doesn't provide a good fit for jettys structures. In cases where jetty only allows one setting, we use the first value. We only initialize KeycloakJettyServerCustomizer if jetty is used, same applies for tomcat. Revised configuration and extracted serverCustomizer code into dedicated classes. Prepared infrastructure for Undertow support.
This commit is contained in:
parent
eba56e4784
commit
be5468a7cf
2 changed files with 180 additions and 62 deletions
|
@ -67,6 +67,27 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-webapp</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -21,20 +21,31 @@ import org.apache.catalina.Context;
|
||||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||||
|
import org.eclipse.jetty.security.ConstraintMapping;
|
||||||
|
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator;
|
||||||
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||||
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
|
||||||
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,24 +72,112 @@ public class KeycloakSpringBootConfiguration {
|
||||||
return new EmbeddedServletContainerCustomizer() {
|
return new EmbeddedServletContainerCustomizer() {
|
||||||
@Override
|
@Override
|
||||||
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
|
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
|
||||||
|
|
||||||
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
|
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
|
||||||
|
|
||||||
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
|
||||||
container.addContextValves(new KeycloakAuthenticatorValve());
|
container.addContextValves(new KeycloakAuthenticatorValve());
|
||||||
|
container.addContextCustomizers(tomcatKeycloakContextCustomizer());
|
||||||
|
|
||||||
container.addContextCustomizers(getTomcatKeycloakContextCustomizer());
|
|
||||||
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
|
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
|
||||||
throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
|
|
||||||
|
UndertowEmbeddedServletContainerFactory container = (UndertowEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
container.addDeploymentInfoCustomizers(undertowKeycloakContextCustomizer());
|
||||||
|
|
||||||
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
|
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
|
||||||
throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented");
|
|
||||||
|
JettyEmbeddedServletContainerFactory container = (JettyEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
container.addServerCustomizers(jettyKeycloakServerCustomizer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() {
|
@ConditionalOnClass(name = {"org.eclipse.jetty.webapp.WebAppContext"})
|
||||||
return new TomcatContextCustomizer() {
|
public JettyServerCustomizer jettyKeycloakServerCustomizer() {
|
||||||
|
return new KeycloakJettyServerCustomizer(keycloakProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
|
||||||
|
public TomcatContextCustomizer tomcatKeycloakContextCustomizer() {
|
||||||
|
return new KeycloakTomcatContextCustomizer(keycloakProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(name = {"io.undertow.Undertow"})
|
||||||
|
public UndertowDeploymentInfoCustomizer undertowKeycloakContextCustomizer() {
|
||||||
|
throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeycloakJettyServerCustomizer implements JettyServerCustomizer {
|
||||||
|
|
||||||
|
private final KeycloakSpringBootProperties keycloakProperties;
|
||||||
|
|
||||||
|
public KeycloakJettyServerCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||||
|
this.keycloakProperties = keycloakProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Server server) {
|
||||||
|
|
||||||
|
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
|
||||||
|
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver());
|
||||||
|
|
||||||
|
List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
|
||||||
|
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||||
|
|
||||||
|
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
|
||||||
|
.getSecurityCollections()) {
|
||||||
|
|
||||||
|
Constraint jettyConstraint = new Constraint();
|
||||||
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
jettyConstraint.setAuthenticate(true);
|
||||||
|
|
||||||
|
if (securityCollectionDefinition.getName() != null) {
|
||||||
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
|
||||||
|
|
||||||
|
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||||
|
if (securityCollectionDefinition.getPatterns().size() > 0) {
|
||||||
|
//First pattern wins
|
||||||
|
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
|
||||||
|
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||||
|
//First method wins
|
||||||
|
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
jettyConstraintMapping.setMethodOmissions(
|
||||||
|
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||||
|
|
||||||
|
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
||||||
|
|
||||||
|
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||||
|
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||||
|
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
||||||
|
|
||||||
|
webAppContext.setHandler(securityHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeycloakTomcatContextCustomizer implements TomcatContextCustomizer {
|
||||||
|
|
||||||
|
private final KeycloakSpringBootProperties keycloakProperties;
|
||||||
|
|
||||||
|
public KeycloakTomcatContextCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||||
|
this.keycloakProperties = keycloakProperties;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(Context context) {
|
public void customize(Context context) {
|
||||||
LoginConfig loginConfig = new LoginConfig();
|
LoginConfig loginConfig = new LoginConfig();
|
||||||
|
@ -134,7 +233,5 @@ public class KeycloakSpringBootConfiguration {
|
||||||
|
|
||||||
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue