Rework security autoconfiguration

This commit combines security autoconfigurations for
management endpoints and the rest of the application. By default,
if Spring Security is on the classpath, it turns on @EnableWebSecurity.
In the presence of another WebSecurityConfigurerAdapter this backs off
completely. A default AuthenticationManager is also provided with a user
and generated password. This can be turned off by specifying a bean of
type AuthenticationManager, AuthenticationProvider or UserDetailsService.

Closes gh-7958
This commit is contained in:
Madhura Bhave
2017-08-27 21:19:34 -07:00
parent f60ad0df74
commit e08ddbf838
83 changed files with 1146 additions and 2212 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfigurationInteg
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
@@ -60,26 +61,18 @@ public class H2ConsoleAutoConfigurationIntegrationTests {
public void noPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build();
mockMvc.perform(get("/h2-console/")).andExpect(status().isUnauthorized());
mockMvc.perform(get("/h2-console/").accept(MediaType.APPLICATION_JSON)).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")))
mockMvc.perform(get("/h2-console/").accept(MediaType.APPLICATION_JSON).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, H2ConsoleAutoConfiguration.class })
@Controller

View File

@@ -21,6 +21,7 @@ import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
@@ -28,6 +29,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -50,7 +52,6 @@ import org.springframework.security.config.annotation.authentication.configurers
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
@@ -72,6 +73,9 @@ public class SecurityAutoConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@Rule
public OutputCapture outputCapture = new OutputCapture();
@After
public void close() {
if (this.context != null) {
@@ -87,9 +91,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
// 1 for static resources and one for the rest
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
.hasSize(2);
.hasSize(1);
}
@Test
@@ -157,7 +160,7 @@ public class SecurityAutoConfigurationTests {
}
@Test
public void testDisableBasicAuthOnApplicationPaths() throws Exception {
public void testDisableDefaultSecurity() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
@@ -166,7 +169,7 @@ public class SecurityAutoConfigurationTests {
this.context.refresh();
// Ignores and the "matches-none" filter only
assertThat(this.context.getBeanNamesForType(FilterChainProxy.class).length)
.isEqualTo(1);
.isEqualTo(0);
}
@Test
@@ -295,33 +298,24 @@ public class SecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class);
this.context.refresh();
SecurityProperties security = this.context.getBean(SecurityProperties.class);
String password = this.outputCapture.toString().split("Using default security password: ")[1].split("\n")[0];
AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
security.getUser().getName(), security.getUser().getPassword());
"user", password);
assertThat(manager.authenticate(token)).isNotNull();
}
@Test
public void testCustomAuthenticationDoesNotAuthenticateWithBootSecurityUser()
public void testCustomAuthenticationDoesNotCreateDefaultUser()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationManagerCustomizer.class,
SecurityAutoConfiguration.class);
this.context.refresh();
SecurityProperties security = this.context.getBean(SecurityProperties.class);
AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
security.getUser().getName(), security.getUser().getPassword());
try {
manager.authenticate(token);
fail("Expected Exception");
}
catch (AuthenticationException success) {
// Expected
}
token = new UsernamePasswordAuthenticationToken("foo", "bar");
assertThat(this.outputCapture.toString()).doesNotContain("Using default security password: ");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("foo", "bar");
assertThat(manager.authenticate(token)).isNotNull();
}

View File

@@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,9 +31,9 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfigurationTests.WebSecurity;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@@ -54,17 +55,19 @@ import org.springframework.web.bind.annotation.RestController;
*/
public class SecurityFilterAutoConfigurationEarlyInitializationTests {
// gh-4154
@Rule
public OutputCapture outputCapture = new OutputCapture();
@Test
public void testSecurityFilterDoesNotCauseEarlyInitialization() throws Exception {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) {
TestPropertyValues.of("server.port:0", "security.user.password:password")
TestPropertyValues.of("server.port:0")
.applyTo(context);
context.register(Config.class);
context.refresh();
int port = context.getWebServer().getPort();
new TestRestTemplate("user", "password")
String password = this.outputCapture.toString().split("Using default security password: ")[1].split("\n")[0];
new TestRestTemplate("user", password)
.getForEntity("http://localhost:" + port, Object.class);
// If early initialization occurred a ConverterNotFoundException is thrown
@@ -76,7 +79,7 @@ public class SecurityFilterAutoConfigurationEarlyInitializationTests {
ConverterBean.class })
@ImportAutoConfiguration({ WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebSecurity.class,
DispatcherServletAutoConfiguration.class,
SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
static class Config {

View File

@@ -17,8 +17,6 @@
package org.springframework.boot.autoconfigure.security;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
@@ -39,63 +37,9 @@ public class SecurityPropertiesTests {
private SecurityProperties security = new SecurityProperties();
@Test
public void testBindingIgnoredSingleValued() {
bind("security.ignored", "/css/**");
assertThat(this.security.getIgnored()).hasSize(1);
}
@Test
public void testBindingIgnoredEmpty() {
bind("security.ignored", "");
assertThat(this.security.getIgnored()).isEmpty();
}
@Test
public void testBindingIgnoredDisable() {
bind("security.ignored", "none");
assertThat(this.security.getIgnored()).hasSize(1);
}
@Test
public void testBindingIgnoredMultiValued() {
bind("security.ignored", "/css/**,/images/**");
assertThat(this.security.getIgnored()).hasSize(2);
}
@Test
public void testBindingIgnoredMultiValuedList() {
Map<String, String> map = new LinkedHashMap<>();
map.put("security.ignored[0]", "/css/**");
map.put("security.ignored[1]", "/foo/**");
MapConfigurationPropertySource source = new MapConfigurationPropertySource(map);
bind(source);
assertThat(this.security.getIgnored()).hasSize(2);
assertThat(this.security.getIgnored().contains("/foo/**")).isTrue();
}
@Test
public void testDefaultPasswordAutogeneratedIfUnresolvedPlaceholder() {
bind("security.user.password", "${ADMIN_PASSWORD}");
assertThat(this.security.getUser().isDefaultPassword()).isTrue();
}
@Test
public void testDefaultPasswordAutogeneratedIfEmpty() {
bind("security.user.password", "");
assertThat(this.security.getUser().isDefaultPassword()).isTrue();
}
@Test
public void testRoles() {
bind("security.user.role", "USER,ADMIN");
assertThat(this.security.getUser().getRole().toString())
.isEqualTo("[USER, ADMIN]");
}
@Test
public void testRole() {
bind("security.user.role", "ADMIN");
assertThat(this.security.getUser().getRole().toString()).isEqualTo("[ADMIN]");
public void testBinding() {
bind("security.basic.enabled", "false");
assertThat(this.security.getBasic().isEnabled()).isFalse();
}
private void bind(String name, String value) {

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2012-2017 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.security;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.EndpointPathResolver;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootSecurity}.
*
* @author Madhura Bhave
*/
public class SpringBootSecurityTests {
private SpringBootSecurity bootSecurity;
private EndpointPathResolver endpointPathResolver = new TestEndpointPathResolver();
private ErrorController errorController = new TestErrorController();
private MockHttpServletRequest request = new MockHttpServletRequest();
private static String[] STATIC_RESOURCES = new String[]{"/css/**", "/js/**",
"/images/**", "/webjars/**", "/**/favicon.ico"};
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() throws Exception {
this.bootSecurity = new SpringBootSecurity(this.endpointPathResolver, this.errorController);
}
@Test
public void endpointIdsShouldThrowIfNoEndpointPaths() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("At least one endpoint id must be specified.");
this.bootSecurity.endpointIds();
}
@Test
public void endpointIdsShouldReturnRequestMatcherWithEndpointPaths() throws Exception {
RequestMatcher requestMatcher = this.bootSecurity.endpointIds("id-1", "id-2");
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
this.request.setServletPath("/test/id-1");
assertThat(requestMatcher.matches(this.request)).isTrue();
this.request.setServletPath("/test/id-2");
assertThat(requestMatcher.matches(this.request)).isTrue();
this.request.setServletPath("/test/other-id");
assertThat(requestMatcher.matches(this.request)).isFalse();
}
@Test
public void endpointIdsShouldReturnRequestMatcherWithAllEndpointPaths() throws Exception {
RequestMatcher requestMatcher = this.bootSecurity.endpointIds(SpringBootSecurity.ALL_ENDPOINTS);
this.request.setServletPath("/test/id-1");
assertThat(requestMatcher.matches(this.request)).isTrue();
this.request.setServletPath("/test/id-2");
assertThat(requestMatcher.matches(this.request)).isTrue();
this.request.setServletPath("/test/other-id");
assertThat(requestMatcher.matches(this.request)).isTrue();
}
@Test
public void endpointsShouldReturnRequestMatcherWithEndpointPaths() throws Exception {
RequestMatcher requestMatcher = this.bootSecurity.endpoints(TestEndpoint1.class);
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
this.request.setServletPath("/test/id-1");
assertThat(requestMatcher.matches(this.request)).isTrue();
this.request.setServletPath("/test/id-2");
assertThat(requestMatcher.matches(this.request)).isFalse();
}
@Test
public void endpointsShouldThrowIfNoEndpointPaths() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("At least one endpoint must be specified.");
this.bootSecurity.endpoints();
}
@Test
public void endpointsShouldThrowExceptionWhenClassNotEndpoint() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Only classes annotated with @Endpoint are supported.");
this.bootSecurity.endpoints(FakeEndpoint.class);
}
@Test
public void staticResourcesShouldReturnRequestMatcherWithStaticResources() throws Exception {
RequestMatcher requestMatcher = this.bootSecurity.staticResources();
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
for (String resource : STATIC_RESOURCES) {
this.request.setServletPath(resource);
assertThat(requestMatcher.matches(this.request)).isTrue();
}
}
@Test
public void errorShouldReturnRequestMatcherWithErrorControllerPath() throws Exception {
RequestMatcher requestMatcher = this.bootSecurity.error();
assertThat(requestMatcher).isInstanceOf(AntPathRequestMatcher.class);
this.request.setServletPath("/test/error");
assertThat(requestMatcher.matches(this.request)).isTrue();
}
@Test
public void errorShouldThrowExceptionWhenNoErrorController() throws Exception {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Path for error controller could not be determined.");
this.bootSecurity = new SpringBootSecurity(this.endpointPathResolver, null);
this.bootSecurity.error();
}
static class TestEndpointPathResolver implements EndpointPathResolver {
@Override
public String resolvePath(String endpointId) {
return "/test/" + endpointId;
}
}
static class TestErrorController implements ErrorController {
@Override
public String getErrorPath() {
return "/test/error";
}
}
@Endpoint(id = "id-1")
static class TestEndpoint1 {
}
@Endpoint(id = "id-2")
static class TestEndpoint2 {
}
static class FakeEndpoint {
}
}

View File

@@ -1,343 +0,0 @@
/*
* Copyright 2012-2017 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.security;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.servlet.Filter;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
* Tests for {@link SpringBootWebSecurityConfiguration}.
*
* @author Dave Syer
* @author Rob Winch
* @author Andy Wilkinson
*/
public class SpringBootWebSecurityConfigurationTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void testWebConfigurationOverrideGlobalAuthentication() throws Exception {
this.context = SpringApplication.run(TestWebConfiguration.class,
"--server.port=0");
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
assertThat(this.context.getBean(AuthenticationManager.class)
.authenticate(new UsernamePasswordAuthenticationToken("dave", "secret")))
.isNotNull();
}
@Test
public void testWebConfigurationFilterChainUnauthenticated() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters(
this.context.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
Matchers.containsString("realm=\"Spring\"")));
}
@Test
public void testWebConfigurationFilterChainUnauthenticatedWithAuthorizeModeNone()
throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0", "--security.basic.authorize-mode=none");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters(
this.context.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isNotFound());
}
@Test
public void testWebConfigurationFilterChainUnauthenticatedWithAuthorizeModeAuthenticated()
throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0", "--security.basic.authorize-mode=authenticated");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters(
this.context.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
Matchers.containsString("realm=\"Spring\"")));
}
@Test
public void testWebConfigurationFilterChainBadCredentials() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters(
this.context.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(
MockMvcRequestBuilders.get("/").header("authorization", "Basic xxx"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
Matchers.containsString("realm=\"Spring\"")));
}
@Test
public void testWebConfigurationInjectGlobalAuthentication() throws Exception {
this.context = SpringApplication.run(TestInjectWebConfiguration.class,
"--server.port=0");
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
assertThat(this.context.getBean(AuthenticationManager.class)
.authenticate(new UsernamePasswordAuthenticationToken("dave", "secret")))
.isNotNull();
}
// gh-3447
@Test
public void testHiddenHttpMethodFilterOrderedFirst() throws Exception {
this.context = SpringApplication.run(DenyPostRequestConfig.class,
"--server.port=0");
int port = Integer
.parseInt(this.context.getEnvironment().getProperty("local.server.port"));
TestRestTemplate rest = new TestRestTemplate();
// not overriding causes forbidden
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
ResponseEntity<Object> result = rest
.postForEntity("http://localhost:" + port + "/", form, Object.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
// override method with GET
form = new LinkedMultiValueMap<>();
form.add("_method", "GET");
result = rest.postForEntity("http://localhost:" + port + "/", form, Object.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void defaultHeaderConfiguration() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters((FilterChainProxy) this.context
.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.header().string("X-Content-Type-Options",
is(notNullValue())))
.andExpect(MockMvcResultMatchers.header().string("X-XSS-Protection",
is(notNullValue())))
.andExpect(MockMvcResultMatchers.header().string("Cache-Control",
is(notNullValue())))
.andExpect(MockMvcResultMatchers.header().string("X-Frame-Options",
is(notNullValue())))
.andExpect(MockMvcResultMatchers.header()
.doesNotExist("Content-Security-Policy"));
}
@Test
public void securityHeadersCanBeDisabled() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--server.port=0", "--security.headers.content-type=false",
"--security.headers.xss=false", "--security.headers.cache=false",
"--security.headers.frame=false");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters(
this.context.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.header()
.doesNotExist("X-Content-Type-Options"))
.andExpect(
MockMvcResultMatchers.header().doesNotExist("X-XSS-Protection"))
.andExpect(MockMvcResultMatchers.header().doesNotExist("Cache-Control"))
.andExpect(
MockMvcResultMatchers.header().doesNotExist("X-Frame-Options"));
}
@Test
public void contentSecurityPolicyConfiguration() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--security.headers.content-security-policy=default-src 'self';",
"--server.port=0");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters((FilterChainProxy) this.context
.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.header()
.string("Content-Security-Policy", is("default-src 'self';")))
.andExpect(MockMvcResultMatchers.header()
.doesNotExist("Content-Security-Policy-Report-Only"));
}
@Test
public void contentSecurityPolicyReportOnlyConfiguration() throws Exception {
this.context = SpringApplication.run(VanillaWebConfiguration.class,
"--security.headers.content-security-policy=default-src 'self';",
"--security.headers.content-security-policy-mode=report-only",
"--server.port=0");
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup((WebApplicationContext) this.context)
.addFilters((FilterChainProxy) this.context
.getBean("springSecurityFilterChain", Filter.class))
.build();
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.header().string(
"Content-Security-Policy-Report-Only", is("default-src 'self';")))
.andExpect(MockMvcResultMatchers.header()
.doesNotExist("Content-Security-Policy"));
}
@Configuration
@Import(TestWebConfiguration.class)
@Order(Ordered.LOWEST_PRECEDENCE)
protected static class TestInjectWebConfiguration
extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder auth;
// It's a bad idea to inject an AuthenticationManager into a
// WebSecurityConfigurerAdapter because it can cascade early instantiation,
// unless you explicitly want the Boot default AuthenticationManager. It's
// better to inject the builder, if you want the global AuthenticationManager. It
// might even be necessary to wrap the builder in a lazy AuthenticationManager
// (that calls getOrBuild() only when the AuthenticationManager is actually
// called).
protected TestInjectWebConfiguration(AuthenticationManagerBuilder auth) {
this.auth = auth;
}
@Override
public void init(WebSecurity web) throws Exception {
this.auth.getOrBuild();
}
}
@MinimalWebConfiguration
@Import(SecurityAutoConfiguration.class)
protected static class VanillaWebConfiguration {
}
@MinimalWebConfiguration
@Import(SecurityAutoConfiguration.class)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class TestWebConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("dave").password("secret")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().denyAll();
}
}
@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
}
@MinimalWebConfiguration
@Import(SecurityAutoConfiguration.class)
protected static class DenyPostRequestConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(HttpMethod.POST, "/**").denyAll();
}
}
}

View File

@@ -40,7 +40,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -76,12 +75,6 @@ public class CustomOAuth2SsoConfigurationTests {
.addFilters(this.filter).build();
}
@Test
public void homePageIsBasicAuth() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", startsWith("Basic")));
}
@Test
public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isFound())

View File

@@ -42,10 +42,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@@ -78,12 +76,6 @@ public class CustomOAuth2SsoWithAuthenticationEntryPointConfigurationTests {
.addFilters(this.filter).build();
}
@Test
public void homePageIsBasicAuth() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", startsWith("Basic")));
}
@Test
public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isUnauthorized());