Skip to content

Commit

Permalink
Add auto-configuration for H2’s web console
Browse files Browse the repository at this point in the history
Three conditions must be met for the console to be enabled:

 - H2 is on the classpath
 - The application is a web application
 - spring.h2.console.enabled is set to true

If spring-boot-devtools is on the classpath, spring.h2.console.enabled
will be set to true automatically. Without the dev tools, the enabled
property will have to be set to true in application.properties.

By default, the console is available at /h2-console. This can be
configured via the spring.h2.console.path property. The value of this
property must begin with a '/'.

When Spring Security is on the classpath the console will be secured
based on the user's security.* configuration. When the console is
secured, CSRF protection is disabled and frame options is set to
SAMEORIGIN for its path. Both settings are required in order for the
console to function.

Closes spring-projectsgh-766
  • Loading branch information
wilkinsona committed Aug 5, 2015
1 parent f95c95a commit 2a5a32b
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 4 deletions.
18 changes: 14 additions & 4 deletions spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
<artifactId>hazelcast-spring</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
Expand Down Expand Up @@ -549,18 +554,23 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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();
}
}

}

}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,\
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {

}
}

}
Loading

0 comments on commit 2a5a32b

Please sign in to comment.