From be5468a7cf2b7ea229430dbb81bfcffee63ebff7 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 12 Jul 2016 01:44:54 +0200 Subject: [PATCH] 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. --- adapters/oidc/spring-boot/pom.xml | 21 ++ .../KeycloakSpringBootConfiguration.java | 221 +++++++++++++----- 2 files changed, 180 insertions(+), 62 deletions(-) diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index c7f322722d..a60441d26e 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -67,6 +67,27 @@ provided + + org.eclipse.jetty + jetty-server + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-security + ${jetty9.version} + provided + + + + org.eclipse.jetty + jetty-webapp + ${jetty9.version} + provided + + junit junit diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java index ec847c019a..68c750c973 100755 --- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java @@ -21,20 +21,31 @@ import org.apache.catalina.Context; import org.apache.tomcat.util.descriptor.web.LoginConfig; import org.apache.tomcat.util.descriptor.web.SecurityCollection; 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.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; 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.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -61,80 +72,166 @@ public class KeycloakSpringBootConfiguration { return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) { + if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) { + TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer; - container.addContextValves(new KeycloakAuthenticatorValve()); + container.addContextCustomizers(tomcatKeycloakContextCustomizer()); - container.addContextCustomizers(getTomcatKeycloakContextCustomizer()); } 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) { - throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented"); + + JettyEmbeddedServletContainerFactory container = (JettyEmbeddedServletContainerFactory) configurableEmbeddedServletContainer; + container.addServerCustomizers(jettyKeycloakServerCustomizer()); } } }; } @Bean - public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() { - return new TomcatContextCustomizer() { - @Override - public void customize(Context context) { - LoginConfig loginConfig = new LoginConfig(); - loginConfig.setAuthMethod("KEYCLOAK"); - context.setLoginConfig(loginConfig); - - Set authRoles = new HashSet(); - for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { - for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { - for (String authRole : collection.getAuthRoles()) { - if (!authRoles.contains(authRole)) { - context.addSecurityRole(authRole); - authRoles.add(authRole); - } - } - } - } - - for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { - SecurityConstraint tomcatConstraint = new SecurityConstraint(); - - for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { - SecurityCollection tomcatSecCollection = new SecurityCollection(); - - if (collection.getName() != null) { - tomcatSecCollection.setName(collection.getName()); - } - if (collection.getDescription() != null) { - tomcatSecCollection.setDescription(collection.getDescription()); - } - - for (String authRole : collection.getAuthRoles()) { - tomcatConstraint.addAuthRole(authRole); - } - - for (String pattern : collection.getPatterns()) { - tomcatSecCollection.addPattern(pattern); - } - - for (String method : collection.getMethods()) { - tomcatSecCollection.addMethod(method); - } - - for (String method : collection.getOmittedMethods()) { - tomcatSecCollection.addOmittedMethod(method); - } - - tomcatConstraint.addCollection(tomcatSecCollection); - } - - context.addConstraint(tomcatConstraint); - } - - context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName()); - } - }; + @ConditionalOnClass(name = {"org.eclipse.jetty.webapp.WebAppContext"}) + 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 jettyConstraintMappings = new ArrayList(); + 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 + public void customize(Context context) { + LoginConfig loginConfig = new LoginConfig(); + loginConfig.setAuthMethod("KEYCLOAK"); + context.setLoginConfig(loginConfig); + + Set authRoles = new HashSet(); + for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { + for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { + for (String authRole : collection.getAuthRoles()) { + if (!authRoles.contains(authRole)) { + context.addSecurityRole(authRole); + authRoles.add(authRole); + } + } + } + } + + for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { + SecurityConstraint tomcatConstraint = new SecurityConstraint(); + + for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { + SecurityCollection tomcatSecCollection = new SecurityCollection(); + + if (collection.getName() != null) { + tomcatSecCollection.setName(collection.getName()); + } + if (collection.getDescription() != null) { + tomcatSecCollection.setDescription(collection.getDescription()); + } + + for (String authRole : collection.getAuthRoles()) { + tomcatConstraint.addAuthRole(authRole); + } + + for (String pattern : collection.getPatterns()) { + tomcatSecCollection.addPattern(pattern); + } + + for (String method : collection.getMethods()) { + tomcatSecCollection.addMethod(method); + } + + for (String method : collection.getOmittedMethods()) { + tomcatSecCollection.addOmittedMethod(method); + } + + tomcatConstraint.addCollection(tomcatSecCollection); + } + + context.addConstraint(tomcatConstraint); + } + + context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName()); + } + } }