Auto-configure MockMvc with Spring Security

When Spring Security and its spring-security-test module are on the
classpath, `@WebMvcTest` will now auto-configure Spring Security and
configure its MockMvc-based test support. This behaviour can be
disabled using the new secure attribute on `@WebMvcTest` and
`@AutoConfigureMockMvc`.

Closes gh-5476
This commit is contained in:
Andy Wilkinson 2016-04-21 15:28:14 +01:00
parent 41ddda4a15
commit 9153ca9526
13 changed files with 260 additions and 10 deletions

View File

@ -101,6 +101,16 @@
<artifactId>spring-restdocs-mockmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>ch.qos.logback</groupId>

View File

@ -43,8 +43,8 @@ import org.springframework.test.web.servlet.MvcResult;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ImportAutoConfiguration({ MockMvcAutoConfiguration.class,
MockMvcWebClientAutoConfiguration.class,
MockMvcWebDriverAutoConfiguration.class })
MockMvcWebClientAutoConfiguration.class, MockMvcWebDriverAutoConfiguration.class,
MockMvcSecurityAutoConfiguration.class })
@PropertyMapping("spring.test.mockmvc")
public @interface AutoConfigureMockMvc {
@ -78,4 +78,11 @@ public @interface AutoConfigureMockMvc {
@PropertyMapping("webdriver.enabled")
boolean webDriverEnabled() default true;
/**
* If Spring Security's {@link MockMvc} support should be auto-configured when it is
* on the classpath. Defaults to {@code true}.
* @return if Spring Security's MockMvc support is auto-configured
*/
boolean secure() default true;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2012-2016 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.test.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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;
/**
* Auto-configuration for Spring Security's testing support.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnProperty(prefix = "spring.test.mockmvc", name = "secure", havingValue = "true", matchIfMissing = true)
@Import({ SecurityAutoConfiguration.class, ServerPropertiesAutoConfiguration.class,
MockMvcSecurityConfiguration.class })
public class MockMvcSecurityAutoConfiguration {
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2012-2016 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.test.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.web.context.WebApplicationContext;
/**
* Configuration for Spring Security's MockMvc integration.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnClass(SecurityMockMvcRequestPostProcessors.class)
class MockMvcSecurityConfiguration {
private static final String DEFAULT_SECURITY_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
@Bean
@ConditionalOnBean(name = DEFAULT_SECURITY_FILTER_NAME)
public SecurityMockMvcBuilderCustomizer securityMockMvcBuilderCustomizer() {
return new SecurityMockMvcBuilderCustomizer();
}
/**
* {@link MockMvcBuilderCustomizer} that ensures that requests run with the user in
* the {@link TestSecurityContextHolder}.
*
* @see SecurityMockMvcRequestPostProcessors#testSecurityContext
*/
class SecurityMockMvcBuilderCustomizer implements MockMvcBuilderCustomizer {
@Override
public void customize(ConfigurableMockMvcBuilder<?> builder) {
builder.apply(new MockMvcConfigurerAdapter() {
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder,
WebApplicationContext context) {
return SecurityMockMvcRequestPostProcessors.testSecurityContext();
}
});
}
}
}

View File

@ -45,9 +45,9 @@ import org.springframework.test.web.servlet.MockMvc;
* {@code WebMvcConfigurer} and {@code HandlerMethodArgumentResolver} beans but not
* {@code @Component}, {@code @Service} or {@code @Repository} beans).
* <p>
* By default, tests annotated with {@code @WebMvcTest} will also auto-configure
* {@link MockMvc} (include support for HtmlUnit WebClient and Selenium WebDriver). For
* more fine-grained control of MockMVC that
* By default, tests annotated with {@code @WebMvcTest} will also auto-configure Spring
* Security and {@link MockMvc} (include support for HtmlUnit WebClient and Selenium
* WebDriver). For more fine-grained control of MockMVC the
* {@link AutoConfigureMockMvc @AutoConfigureMockMvc} annotation can be used.
* <p>
* Typically {@code @WebMvcTest} is used in combination with {@link MockBean @MockBean} or
@ -118,4 +118,12 @@ public @interface WebMvcTest {
*/
Filter[] excludeFilters() default {};
/**
* If Spring Security's {@link MockMvc} support should be auto-configured when it is
* on the classpath. Defaults to {@code true}.
* @return if Spring Security's MockMvc support is auto-configured
*/
@AliasFor(annotation = AutoConfigureMockMvc.class, attribute = "secure")
boolean secure() default true;
}

View File

@ -44,7 +44,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
* @author Andy Wilkinson
*/
@RunWith(SpringRunner.class)
@WebMvcTest(RestDocsTestController.class)
@WebMvcTest(controllers = RestDocsTestController.class, secure = false)
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class RestDocsAutoConfigurationAdvancedConfigurationIntegrationTests {

View File

@ -0,0 +1,71 @@
/*
* Copyright 2012-2016 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.test.autoconfigure.security;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.Base64Utils;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for {@link MockMvcSecurityAutoConfiguration}.
*
* @author Andy Wilkinson
*/
@WebMvcTest
@RunWith(SpringRunner.class)
@TestPropertySource(properties = { "security.user.password=secret", "debug=true" })
public class MockMvcSecurityAutoConfigurationIntegrationTests {
@Autowired
MockMvc mockMvc;
@Autowired
ConditionEvaluationReport conditionEvaluationReport;
@Test
@WithMockUser(username = "test", password = "test", roles = "USER")
public void okResponseWithMockUser() throws Exception {
this.mockMvc.perform(get("/")).andExpect(status().isOk());
}
@Test
public void unauthorizedResponseWithNoUser() throws Exception {
this.mockMvc.perform(get("/")).andExpect(status().isUnauthorized());
}
@Test
public void okResponseWithBasicAuthCredentialsForKnownUser() throws Exception {
this.mockMvc
.perform(get("/").header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64Utils.encodeToString("user:secret".getBytes())))
.andExpect(status().isOk());
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-2016 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.test.autoconfigure.security;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests application for {@link MockMvcSecurityAutoConfiguration}.
*
* @author Andy Wilkinson
*/
@SpringBootApplication
public class SecurityTestApplication {
@RestController
static class MyController {
@RequestMapping("/")
@Secured("ROLE_USER")
public String index() {
return "Hello";
}
}
}

View File

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationContext;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@ -40,6 +41,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(alwaysPrint = false)
@WithMockUser(username = "user", password = "secret")
public class MockMvcSpringBootTestIntegrationTests {
@MockBean

View File

@ -33,7 +33,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@WebMvcTest
@WebMvcTest(secure = false)
public class WebMvcTestAllControllersIntegrationTests {
@Autowired

View File

@ -33,7 +33,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@WebMvcTest(ExampleController2.class)
@WebMvcTest(controllers = ExampleController2.class, secure = false)
public class WebMvcTestOneControllerIntegrationTests {
@Autowired

View File

@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@WebMvcTest
@WebMvcTest(secure = false)
public class WebMvcTestWebClientIntegrationTests {
@Autowired

View File

@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@WebMvcTest
@WebMvcTest(secure = false)
public class WebMvcTestWebDriverIntegrationTests {
@Autowired