diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index bedbc8262d..a68effcaf0 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -70,6 +70,11 @@ hazelcast-spring true + + com.h2database + h2 + true + com.samskivert jmustache @@ -549,8 +554,9 @@ test - org.yaml - snakeyaml + org.slf4j + slf4j-jdk14 + test org.springframework @@ -558,9 +564,13 @@ test - org.slf4j - slf4j-jdk14 + org.springframework.security + spring-security-test test + + org.yaml + snakeyaml + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.java new file mode 100644 index 0000000000..944c9d2e65 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.h2; + +import org.h2.server.web.WebServlet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.SecurityAuthorizeMode; +import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for H2's web console + * + * @author Andy Wilkinson + * @since 1.3.0 + */ +@Configuration +@ConditionalOnWebApplication +@ConditionalOnClass(WebServlet.class) +@ConditionalOnProperty(prefix = "spring.h2.console", name = "enabled", havingValue = "true", matchIfMissing = false) +@EnableConfigurationProperties(H2ConsoleProperties.class) +@AutoConfigureAfter(SecurityAutoConfiguration.class) +public class H2ConsoleAutoConfiguration { + + @Autowired + private H2ConsoleProperties properties; + + @Bean + public ServletRegistrationBean h2Console() { + return new ServletRegistrationBean(new WebServlet(), this.properties.getPath() + .endsWith("/") ? this.properties.getPath() + "*" + : this.properties.getPath() + "/*"); + } + + @Configuration + @ConditionalOnClass(WebSecurityConfigurerAdapter.class) + @ConditionalOnBean(ObjectPostProcessor.class) + @ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true) + static class H2ConsoleSecurityConfiguration { + + @Bean + public WebSecurityConfigurerAdapter h2ConsoleSecurityConfigurer() { + return new H2ConsoleSecurityConfigurer(); + } + + @Order(SecurityProperties.BASIC_AUTH_ORDER - 10) + private static class H2ConsoleSecurityConfigurer extends + WebSecurityConfigurerAdapter { + + @Autowired + private H2ConsoleProperties console; + + @Autowired + private SecurityProperties security; + + @Override + public void configure(HttpSecurity http) throws Exception { + HttpSecurity h2Console = http.antMatcher(this.console.getPath().endsWith( + "/") ? this.console.getPath() + "**" : this.console.getPath() + + "/**"); + h2Console.csrf().disable(); + h2Console.httpBasic(); + h2Console.headers().frameOptions().sameOrigin(); + String[] roles = this.security.getUser().getRole().toArray(new String[0]); + SecurityAuthorizeMode mode = this.security.getBasic().getAuthorizeMode(); + if (mode == null || mode == SecurityAuthorizeMode.ROLE) { + http.authorizeRequests().anyRequest().hasAnyRole(roles); + } + else if (mode == SecurityAuthorizeMode.AUTHENTICATED) { + http.authorizeRequests().anyRequest().authenticated(); + } + } + + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleProperties.java new file mode 100644 index 0000000000..498706ad23 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleProperties.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.h2; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for H2's console + * + * @author Andy Wilkinson + * @since 1.3.0 + */ +@ConfigurationProperties(prefix = "spring.h2.console") +public class H2ConsoleProperties { + + /** + * Path at which the console will be available. + */ + @NotNull + @Pattern(regexp = "/[^?#]*", message = "Path must start with /") + private String path = "/h2-console"; + + /** + * Enable the console. + */ + private boolean enabled = false; + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index efe6b7a887..6fd13ee103 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -22,6 +22,7 @@ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfigurat org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ +org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationIntegrationTests.java new file mode 100644 index 0000000000..a7299b2d4b --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.h2; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfigurationIntegrationTests.TestConfiguration; +import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Controller; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration tests for {@link H2ConsoleAutoConfiguration} + * + * @author Andy Wilkinson + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = TestConfiguration.class) +@TestPropertySource(properties = "spring.h2.console.enabled:true") +public class H2ConsoleAutoConfigurationIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Test + public void noPrincipal() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(springSecurity()).build(); + mockMvc.perform(get("/h2-console/")).andExpect(status().isUnauthorized()); + } + + @Test + public void userPrincipal() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(springSecurity()).build(); + mockMvc.perform(get("/h2-console/").with(user("test").roles("USER"))) + .andExpect(status().isOk()) + .andExpect(header().string("X-Frame-Options", "SAMEORIGIN")); + } + + @Test + public void someOtherPrincipal() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(springSecurity()).build(); + mockMvc.perform(get("/h2-console/").with(user("test").roles("FOO"))).andExpect( + status().isForbidden()); + } + + @Configuration + @Import({ SecurityAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, + H2ConsoleAutoConfiguration.class }) + @Controller + static class TestConfiguration { + + @RequestMapping("/h2-console/**") + public void mockConsole() { + + } + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationTests.java new file mode 100644 index 0000000000..04ad734e90 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.h2; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link H2ConsoleAutoConfiguration} + * + * @author Andy Wilkinson + */ +public class H2ConsoleAutoConfigurationTests { + + private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setupContext() { + this.context.setServletContext(new MockServletContext()); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void consoleIsDisabledByDefault() { + this.context.register(H2ConsoleAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ServletRegistrationBean.class).size(), + is(equalTo(0))); + } + + @Test + public void propertyCanEnableConsole() { + this.context.register(H2ConsoleAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.h2.console.enabled:true"); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ServletRegistrationBean.class).size(), + is(equalTo(1))); + assertThat(this.context.getBean(ServletRegistrationBean.class).getUrlMappings(), + hasItems("/h2-console/*")); + } + + @Test + public void customPathMustBeginWithASlash() { + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("Path must start with /"); + this.context.register(H2ConsoleAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.h2.console.enabled:true", "spring.h2.console.path:custom"); + this.context.refresh(); + } + + @Test + public void customPathWithTrailingSlash() { + this.context.register(H2ConsoleAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.h2.console.enabled:true", "spring.h2.console.path:/custom/"); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ServletRegistrationBean.class).size(), + is(equalTo(1))); + assertThat(this.context.getBean(ServletRegistrationBean.class).getUrlMappings(), + hasItems("/custom/*")); + } + + @Test + public void customPath() { + this.context.register(H2ConsoleAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.h2.console.enabled:true", "spring.h2.console.path:/custom"); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ServletRegistrationBean.class).size(), + is(equalTo(1))); + assertThat(this.context.getBean(ServletRegistrationBean.class).getUrlMappings(), + hasItems("/custom/*")); + } +} diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsPropertyDefaultsPostProcessor.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsPropertyDefaultsPostProcessor.java index 45815b8221..785b65a12c 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsPropertyDefaultsPostProcessor.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsPropertyDefaultsPostProcessor.java @@ -47,6 +47,7 @@ class DevToolsPropertyDefaultsPostProcessor implements BeanFactoryPostProcessor, properties.put("spring.velocity.cache", "false"); properties.put("spring.mustache.cache", "false"); properties.put("server.session.persistent", "true"); + properties.put("spring.h2.console.enabled", "true"); PROPERTIES = Collections.unmodifiableMap(properties); } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 64d40d1647..4d28ce6125 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -162,6 +162,10 @@ content into your application; rather pick only the properties that you need. multipart.max-file-size=1Mb # Max file size. multipart.max-request-size=10Mb # Max request size. + # H2 Web Console ({sc-spring-boot-autoconfigure}/h2/H2ConsoleProperties.{sc-ext}[H2ConsoleProperties]) + spring.h2.console.enable=false # Enable the console + spring.h2.console.path=/h2-console # Path at which the console can be accessed + # SPRING HATEOAS ({sc-spring-boot-autoconfigure}/hateoas/HateoasProperties.{sc-ext}[HateoasProperties]) spring.hateoas.apply-to-primary-object-mapper=true # if the primary mapper should also be configured diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 130f20cacc..6bfb832a9b 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2258,6 +2258,45 @@ Hibernate autoconfig is active because the `ddl-auto` settings are more fine-gra +[[boot-features-sql-h2-console]] +=== Using H2's web console +The http://www.h2database.com[H2 database] provides a +http://www.h2database.com/html/quickstart.html#h2_console[browser-based console] that +Spring Boot can auto-configure for you. The console will be auto-configured when the +following conditions are met: + +* You are developing a web application +* `com.h2database:h2` is on the classpath +* You are using <> + +TIP: If you are not using Spring Boot's developer tools, but would still like to make use +of H2's console, then you can do so by configuring the `spring.h2.console.enabled` +property with a value of `true`. The H2 console is only intended for use during +development so care should be taken to ensure that `spring.h2.console.enabled` is not set +to `true` in production. + + + +[[boot-features-sql-h2-console-custom-path]] +==== Changing the H2 console's path +By default the console will be available at `/h2-console`. You can customize the console's +path using the `spring.h2.console.path` property. + + + +[[boot-features-sql-h2-console-securing]] +==== Securing the H2 console +When Spring Security is one the and basic auth is enabled, the H2 console will be +automatically secured using basic auth. The following properties can be used to customize +the security configuration: + +* `security.user.role` +* `security.basic.authorize-mode` +* `security.basic.enabled` + + + [[boot-features-jooq]] == Using jOOQ Java Object Oriented Querying (http://www.jooq.org/[jOOQ]) is a popular product from