From 997b015d108ef4330e30bb673ce4fb27e9b25e5a Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 17 Dec 2013 17:15:05 +0000 Subject: [PATCH] Split SecurityAutoConfiguration Spring Security 3.2 has a new annotation @EnableWebMvcSecurity that we should use if MVC is being used. --- .../security/SecurityAutoConfiguration.java | 25 +++- spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-servlet/pom.xml | 40 ++++++ .../servlet/SampleServletApplication.java | 53 ++++++++ .../SampleServletApplicationTests.java | 117 ++++++++++++++++++ 5 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 spring-boot-samples/spring-boot-sample-servlet/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-servlet/src/main/java/org/springframework/boot/sample/servlet/SampleServletApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-servlet/src/test/java/org/springframework/boot/sample/servlet/SampleServletApplicationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java index 106a11e4b42..3d8688f4d62 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -46,10 +47,12 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.header.writers.HstsHeaderWriter; import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.web.servlet.support.RequestDataValueProcessor; /** * {@link EnableAutoConfiguration Auto-configuration} for security of a web application or @@ -129,10 +132,30 @@ public class SecurityAutoConfiguration { } - @ConditionalOnMissingBean({ ApplicationWebSecurityConfigurerAdapter.class }) + // Pull in @EnableWebMvcSecurity if Spring MVC is available and no-one defined a + // RequestDataValueProcessor + @ConditionalOnClass(RequestDataValueProcessor.class) + @ConditionalOnMissingBean(RequestDataValueProcessor.class) + @ConditionalOnExpression("${security.basic.enabled:true}") + @Configuration + protected static class WebMvcSecurityConfigurationConditions { + @Configuration + @EnableWebMvcSecurity + protected static class DefaultWebMvcSecurityConfiguration { + } + } + + // Pull in a plain @EnableWebSecurity if Spring MVC is not available + @ConditionalOnMissingBean(WebMvcSecurityConfigurationConditions.class) + @ConditionalOnMissingClass(name = "org.springframework.web.servlet.support.RequestDataValueProcessor") @ConditionalOnExpression("${security.basic.enabled:true}") @Configuration @EnableWebSecurity + protected static class DefaultWebSecurityConfiguration { + } + + @ConditionalOnExpression("${security.basic.enabled:true}") + @Configuration @Order(Ordered.LOWEST_PRECEDENCE - 5) protected static class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index d1dd8b0b1e7..752fe1ab226 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -26,6 +26,7 @@ spring-boot-sample-jetty spring-boot-sample-profile spring-boot-sample-secure + spring-boot-sample-servlet spring-boot-sample-simple spring-boot-sample-tomcat spring-boot-sample-traditional diff --git a/spring-boot-samples/spring-boot-sample-servlet/pom.xml b/spring-boot-samples/spring-boot-sample-servlet/pom.xml new file mode 100644 index 00000000000..e1aa379fa3a --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-servlet/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 0.5.0.BUILD-SNAPSHOT + + spring-boot-sample-servlet + war + + ${basedir}/../.. + + + + ${project.groupId} + spring-boot-starter + + + ${project.groupId} + spring-boot-starter-security + + + ${project.groupId} + spring-boot-starter-tomcat + ${project.version} + provided + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-servlet/src/main/java/org/springframework/boot/sample/servlet/SampleServletApplication.java b/spring-boot-samples/spring-boot-sample-servlet/src/main/java/org/springframework/boot/sample/servlet/SampleServletApplication.java new file mode 100644 index 00000000000..770e6c455d1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-servlet/src/main/java/org/springframework/boot/sample/servlet/SampleServletApplication.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2013 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.sample.servlet; + +import java.io.IOException; + +import javax.servlet.GenericServlet; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +public class SampleServletApplication { + + @SuppressWarnings("serial") + @Bean + public Servlet dispatcherServlet() { + return new GenericServlet() { + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, + IOException { + res.setContentType("text/plain"); + res.getWriter().append("Hello World"); + } + }; + } + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleServletApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-servlet/src/test/java/org/springframework/boot/sample/servlet/SampleServletApplicationTests.java b/spring-boot-samples/spring-boot-sample-servlet/src/test/java/org/springframework/boot/sample/servlet/SampleServletApplicationTests.java new file mode 100644 index 00000000000..70a9cc58ea6 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-servlet/src/test/java/org/springframework/boot/sample/servlet/SampleServletApplicationTests.java @@ -0,0 +1,117 @@ +package org.springframework.boot.sample.servlet; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +/** + * Basic integration tests for demo application. + * + * @author Dave Syer + */ +public class SampleServletApplicationTests { + + private static ConfigurableApplicationContext context; + + @BeforeClass + public static void start() throws Exception { + Future future = Executors + .newSingleThreadExecutor().submit( + new Callable() { + @Override + public ConfigurableApplicationContext call() throws Exception { + return SpringApplication + .run(SampleServletApplication.class); + } + }); + context = future.get(60, TimeUnit.SECONDS); + } + + @AfterClass + public static void stop() { + if (context != null) { + context.close(); + } + } + + @Test + public void testHomeIsSecure() throws Exception { + ResponseEntity entity = getRestTemplate().getForEntity( + "http://localhost:8080", String.class); + assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode()); + } + + @Test + public void testHome() throws Exception { + ResponseEntity entity = getRestTemplate("user", getPassword()).getForEntity( + "http://localhost:8080", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertEquals("Hello World", entity.getBody()); + } + + private String getPassword() { + return context.getBean(SecurityProperties.class).getUser().getPassword(); + } + + private RestTemplate getRestTemplate() { + return getRestTemplate(null, null); + } + + private RestTemplate getRestTemplate(final String username, final String password) { + + List interceptors = new ArrayList(); + + if (username != null) { + + interceptors.add(new ClientHttpRequestInterceptor() { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().add( + "Authorization", + "Basic " + + new String(Base64 + .encode((username + ":" + password) + .getBytes()))); + return execution.execute(request, body); + } + }); + } + + RestTemplate restTemplate = new RestTemplate( + new InterceptingClientHttpRequestFactory( + new SimpleClientHttpRequestFactory(), interceptors)); + restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { + @Override + public void handleError(ClientHttpResponse response) throws IOException { + } + }); + return restTemplate; + + } +}