keycloak-developers
Changes
integration/pom.xml 1(+1 -0)
integration/spring-boot/pom.xml 77(+77 -0)
integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java 28(+28 -0)
integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java 123(+123 -0)
Details
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 7b72d6a..b5c21ab 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -15,6 +15,7 @@
<!ENTITY Jetty9Adapter SYSTEM "modules/jetty9-adapter.xml">
<!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
<!ENTITY FuseAdapter SYSTEM "modules/fuse-adapter.xml">
+ <!ENTITY SpringBootAdapter SYSTEM "modules/spring-boot-adapter.xml">
<!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY SAML SYSTEM "modules/saml.xml">
@@ -95,6 +96,7 @@ This one is short
&Jetty8Adapter;
&FuseAdapter;
&JavascriptAdapter;
+ &SpringBootAdapter;
&InstalledApplications;
&Logout;
&MultiTenancy;
diff --git a/docbook/reference/en/en-US/modules/spring-boot-adapter.xml b/docbook/reference/en/en-US/modules/spring-boot-adapter.xml
new file mode 100755
index 0000000..6728e03
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/spring-boot-adapter.xml
@@ -0,0 +1,72 @@
+<section id="spring-boot-adapter">
+ <title>Spring Boot Adapter</title>
+ <para>
+ To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter
+ JAR to your app. You then have to provide some extra configuration via normal Spring
+ Boot configuration (<literal>application.properties</literal>). Let's go over these steps.
+ </para>
+ <section id="spring-boot-adapter-installation">
+ <title>Adapter Installation</title>
+ <para>
+ The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all
+ you need to do is add the Keycloak Spring Boot adapter JAR to your project. Depending on
+ what container you are using with Spring Boot, you also need to add the appropriate
+ Keycloak container adapter. If you are using Maven, add the following to your pom.xml (using
+ Tomcat as an example):
+ </para>
+ <para>
+ <programlisting>
+<![CDATA[
+<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-spring-boot-adapter</artifactId>
+ <version>1.2.0.Beta1-SNAPSHOT</version>
+</dependency>
+<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat8-adapter</artifactId>
+ <version>${keycloak.version}</version>
+</dependency>
+]]>
+ </programlisting>
+ </para>
+ </section>
+
+ <section id="spring-boot-adapter-configuration">
+ <title>Required Spring Boot Adapter Configuration</title>
+ <para>
+ This section describes how to configure your Spring Boot app to use Keycloak.
+ </para>
+ <para>
+ Instead of a <literal>keycloak.json</literal> file, you configure the realm for the Spring
+ Boot Keycloak adapter via the normal Spring Boot configuration. For example:
+ </para>
+ <programlisting>
+<![CDATA[
+keycloak.realm = demorealm
+keycloak.realmKey = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCWYuxXmsmfV+Xc9Ik8QET8lD4wuHrJAXbbutS2O/eMjQQLNK7QDX/k/XhOkhxP0YBEypqeXeGaeQJjCxDhFjJXQuewUEMlmSja3IpoJ9/hFn4Cns4m7NGO+rtvnfnwgVfsEOS5EmZhRddp+40KBPPJfTH6Vgu6KjQwuFPj6DTwIDAQAB
+keycloak.auth-server-url = http://127.0.0.1:8080/auth
+keycloak.ssl-required = external
+keycloak.resource = demoapp
+keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111
+keycloak.use-resource-role-mappings = true
+]]>
+ </programlisting>
+ <para>
+ You also need to specify the J2EE security config that would normally go in the <literal>web.xml</literal>.
+ Here's an example configuration:
+ </para>
+ <programlisting>
+<![CDATA[
+keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff
+keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin
+keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = user
+keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure
+
+keycloak.securityConstraints[0].securityCollections[1].name = admin stuff
+keycloak.securityConstraints[0].securityCollections[1].authRoles[0] = admin
+keycloak.securityConstraints[0].securityCollections[1].patterns[0] = /admin
+]]>
+ </programlisting>
+ </section>
+</section>
\ No newline at end of file
integration/pom.xml 1(+1 -0)
diff --git a/integration/pom.xml b/integration/pom.xml
index c14ec59..2ccda8e 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -29,5 +29,6 @@
<module>installed</module>
<module>admin-client</module>
<module>osgi-adapter</module>
+ <module>spring-boot</module>
</modules>
</project>
integration/spring-boot/pom.xml 77(+77 -0)
diff --git a/integration/spring-boot/pom.xml b/integration/spring-boot/pom.xml
new file mode 100755
index 0000000..22552cb
--- /dev/null
+++ b/integration/spring-boot/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<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">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.2.0.Beta1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-spring-boot-adapter</artifactId>
+ <name>Keycloak Spring Boot Integration</name>
+ <description/>
+
+ <properties>
+ <spring-boot.version>1.2.1.RELEASE</spring-boot.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat8-adapter</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty92-adapter</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>${spring-boot.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java
new file mode 100644
index 0000000..c5834c2
--- /dev/null
+++ b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java
@@ -0,0 +1,28 @@
+package org.keycloak.adapters.springboot;
+
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+
+public class KeycloakSpringBootConfigResolver implements org.keycloak.adapters.KeycloakConfigResolver {
+
+ private KeycloakDeployment keycloakDeployment;
+
+ private static AdapterConfig adapterConfig;
+
+ @Override
+ public KeycloakDeployment resolve(HttpFacade.Request request) {
+ if (keycloakDeployment != null) {
+ return keycloakDeployment;
+ }
+
+ keycloakDeployment = KeycloakDeploymentBuilder.build(KeycloakSpringBootConfigResolver.adapterConfig);
+
+ return keycloakDeployment;
+ }
+
+ static void setAdapterConfig(AdapterConfig adapterConfig) {
+ KeycloakSpringBootConfigResolver.adapterConfig = adapterConfig;
+ }
+}
\ No newline at end of file
diff --git a/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java
new file mode 100755
index 0000000..f48929d
--- /dev/null
+++ b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java
@@ -0,0 +1,123 @@
+package org.keycloak.adapters.springboot;
+
+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.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
+import org.springframework.beans.factory.annotation.Autowired;
+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.tomcat.TomcatContextCustomizer;
+import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
+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.HashSet;
+import java.util.Set;
+
+/**
+ * Keycloak authentication integration for Spring Boot
+ *
+ * @author <a href="mailto:jimmidyson@gmail.com">Jimmi Dyson</a>
+ * @version $Revision: 1 $
+ */
+@Configuration
+@ConditionalOnWebApplication
+@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
+public class KeycloakSpringBootConfiguration {
+
+ private KeycloakSpringBootProperties keycloakProperties;
+
+ @Autowired
+ public void setKeycloakSpringBootProperties(KeycloakSpringBootProperties keycloakProperties) {
+ this.keycloakProperties = keycloakProperties;
+ KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties);
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() {
+ return new EmbeddedServletContainerCustomizer() {
+ @Override
+ public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
+ if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
+ TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
+
+ container.addContextValves(new KeycloakAuthenticatorValve());
+
+ container.addContextCustomizers(getTomcatKeycloakContextCustomizer());
+ } else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
+ throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
+ } else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
+ throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented");
+ }
+ }
+ };
+ }
+
+ @Bean
+ public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() {
+ return new TomcatContextCustomizer() {
+ @Override
+ public void customize(Context context) {
+ LoginConfig loginConfig = new LoginConfig();
+ loginConfig.setAuthMethod("KEYCLOAK");
+ context.setLoginConfig(loginConfig);
+
+ Set<String> authRoles = new HashSet<String>();
+ 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());
+ }
+ };
+ }
+
+}
diff --git a/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java
new file mode 100644
index 0000000..18ec791
--- /dev/null
+++ b/integration/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java
@@ -0,0 +1,90 @@
+package org.keycloak.adapters.springboot;
+
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false)
+public class KeycloakSpringBootProperties extends AdapterConfig {
+
+ private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
+
+ public static class SecurityConstraint {
+ private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>();
+
+ public List<SecurityCollection> getSecurityCollections() {
+ return securityCollections;
+ }
+
+ public void setSecurityCollections(List<SecurityCollection> securityCollections) {
+ this.securityCollections = securityCollections;
+ }
+ }
+
+ public static class SecurityCollection {
+ private String name;
+ private String description;
+ private List<String> authRoles = new ArrayList<String>();
+ private List<String> patterns = new ArrayList<String>();
+ private List<String> methods = new ArrayList<String>();
+ private List<String> omittedMethods = new ArrayList<String>();
+
+ public List<String> getAuthRoles() {
+ return authRoles;
+ }
+
+ public List<String> getPatterns() {
+ return patterns;
+ }
+
+ public List<String> getMethods() {
+ return methods;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<String> getOmittedMethods() {
+ return omittedMethods;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setAuthRoles(List<String> authRoles) {
+ this.authRoles = authRoles;
+ }
+
+ public void setPatterns(List<String> patterns) {
+ this.patterns = patterns;
+ }
+
+ public void setMethods(List<String> methods) {
+ this.methods = methods;
+ }
+
+ public void setOmittedMethods(List<String> omittedMethods) {
+ this.omittedMethods = omittedMethods;
+ }
+ }
+
+ public List<SecurityConstraint> getSecurityConstraints() {
+ return securityConstraints;
+ }
+
+ public void setSecurityConstraints(List<SecurityConstraint> securityConstraints) {
+ this.securityConstraints = securityConstraints;
+ }
+}
diff --git a/integration/spring-boot/src/main/resources/META-INF/spring.factories b/integration/spring-boot/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..3193ee6
--- /dev/null
+++ b/integration/spring-boot/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
\ No newline at end of file