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()); + } + } }