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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user