Use IgnoredRequestCustomizer to ignore paths

Update `SpringBootWebSecurityConfiguration` to ignore requests by
delegating to `IgnoredRequestCustomizer` beans. This allows a single
Spring Boot `WebSecurityConfigurer<WebSecurity>` bean to be used which
prevents potential exceptions caused by duplicate `@Order` values.

Fixes gh-7106
pull/7630/head
Madhura Bhave 8 years ago
parent d09aafacda
commit 95be208f0f

@ -26,7 +26,6 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
@ -42,11 +41,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration;
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityPrerequisite;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
@ -56,9 +55,7 @@ import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
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.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
@ -72,7 +69,6 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@ -102,9 +98,34 @@ public class ManagementWebSecurityAutoConfiguration {
AnyRequestMatcher.INSTANCE);
@Bean
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter() {
return new IgnoredPathsWebSecurityConfigurerAdapter();
public IgnoredRequestCustomizer managementIgnoredRequestCustomizer(
ManagementServerProperties management,
ObjectProvider<ManagementContextResolver> contextResolverProvider) {
return new ManagementIgnoredRequestCustomizer(management,
contextResolverProvider.getIfAvailable());
}
private class ManagementIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
private final ManagementServerProperties management;
private final ManagementContextResolver contextResolver;
ManagementIgnoredRequestCustomizer(ManagementServerProperties management,
ManagementContextResolver contextResolver) {
this.management = management;
this.contextResolver = contextResolver;
}
@Override
public void customize(IgnoredRequestConfigurer configurer) {
if (!this.management.getSecurity().isEnabled()) {
RequestMatcher requestMatcher = LazyEndpointPathRequestMatcher
.getRequestMatcher(this.contextResolver);
configurer.requestMatchers(requestMatcher);
}
}
}
@Configuration
@ -132,80 +153,6 @@ public class ManagementWebSecurityAutoConfiguration {
}
// Get the ignored paths in early
@Order(SecurityProperties.IGNORED_ORDER + 1)
private static class IgnoredPathsWebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
@Autowired(required = false)
private ErrorController errorController;
@Autowired
private SecurityProperties security;
@Autowired
private ManagementServerProperties management;
@Autowired(required = false)
private ManagementContextResolver contextResolver;
@Autowired(required = false)
private ServerProperties server;
@Override
public void configure(WebSecurity builder) throws Exception {
}
@Override
public void init(WebSecurity builder) throws Exception {
if (this.server == null) {
return;
}
IgnoredRequestConfigurer ignoring = builder.ignoring();
// The ignores are not cumulative, so to prevent overwriting the defaults
// we add them back.
Set<String> ignored = new LinkedHashSet<String>(
SpringBootWebSecurityConfiguration.getIgnored(this.security));
if (ignored.contains("none")) {
ignored.remove("none");
}
if (this.errorController != null) {
ignored.add(normalizePath(this.errorController.getErrorPath()));
}
RequestMatcher requestMatcher = getRequestMatcher();
String[] paths = this.server.getPathsArray(ignored);
if (!ObjectUtils.isEmpty(paths)) {
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
for (String pattern : paths) {
matchers.add(new AntPathRequestMatcher(pattern, null));
}
if (requestMatcher != null) {
matchers.add(requestMatcher);
}
requestMatcher = new OrRequestMatcher(matchers);
}
if (requestMatcher != null) {
ignoring.requestMatchers(requestMatcher);
}
}
private RequestMatcher getRequestMatcher() {
if (this.management.getSecurity().isEnabled()) {
return null;
}
return LazyEndpointPathRequestMatcher.getRequestMatcher(this.contextResolver);
}
private String normalizePath(String errorPath) {
String result = StringUtils.cleanPath(errorPath);
if (!result.startsWith("/")) {
result = "/" + result;
}
return result;
}
}
@Configuration
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@Conditional(WebSecurityEnablerCondition.class)
@ -310,9 +257,7 @@ public class ManagementWebSecurityAutoConfiguration {
// Permit access to the non-sensitive endpoints
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
this.contextResolver, EndpointPaths.NON_SENSITIVE)).permitAll();
// Restrict the rest to the configured roles
List<String> roles = this.management.getSecurity().getRoles();
requests.anyRequest().hasAnyRole(roles.toArray(new String[roles.size()]));
requests.anyRequest().authenticated();
}
}

@ -28,12 +28,15 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerInterceptor;
@ -95,4 +98,20 @@ public class CloudFoundryActuatorAutoConfiguration {
return corsConfiguration;
}
@Bean
public IgnoredRequestCustomizer cloudFoundryIgnoredRequestCustomizer() {
return new CloudFoundryIgnoredRequestCustomizer();
}
private class CloudFoundryIgnoredRequestCustomizer
implements IgnoredRequestCustomizer {
@Override
public void customize(WebSecurity.IgnoredRequestConfigurer configurer) {
configurer.requestMatchers(
new AntPathRequestMatcher("/cloudfoundryapplication/**"));
}
}
}

@ -21,6 +21,7 @@ import java.util.Arrays;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
@ -29,18 +30,24 @@ import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties
import org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
@ -116,10 +123,9 @@ public class CloudFoundryActuatorAutoConfigurationTests {
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
"vcap.application.application_id:my-app-id");
this.context.refresh();
CloudFoundryEndpointHandlerMapping handlerMapping1 = this.context.getBean(
CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean(
"cloudFoundryEndpointHandlerMapping",
CloudFoundryEndpointHandlerMapping.class);
CloudFoundryEndpointHandlerMapping handlerMapping = handlerMapping1;
Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping,
"securityInterceptor");
Object interceptorSecurityService = ReflectionTestUtils
@ -127,13 +133,24 @@ public class CloudFoundryActuatorAutoConfigurationTests {
assertThat(interceptorSecurityService).isNull();
}
private CloudFoundryEndpointHandlerMapping getHandlerMapping() {
@Test
public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
"vcap.application.application_id:my-app-id",
"vcap.application.cf_api:http://my-cloud-controller.com");
"vcap.application.application_id:my-app-id");
this.context.refresh();
return this.context.getBean("cloudFoundryEndpointHandlerMapping",
CloudFoundryEndpointHandlerMapping.class);
IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context
.getBean("cloudFoundryIgnoredRequestCustomizer");
IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class);
customizer.customize(configurer);
ArgumentCaptor<RequestMatcher> requestMatcher = ArgumentCaptor
.forClass(RequestMatcher.class);
verify(configurer).requestMatchers(requestMatcher.capture());
RequestMatcher matcher = requestMatcher.getValue();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/cloudfoundryapplication/my-path");
assertThat(matcher.matches(request)).isTrue();
request.setServletPath("/some-other-path");
assertThat(matcher.matches(request)).isFalse();
}
@Test
@ -152,4 +169,13 @@ public class CloudFoundryActuatorAutoConfigurationTests {
.isFalse();
}
private CloudFoundryEndpointHandlerMapping getHandlerMapping() {
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
"vcap.application.application_id:my-app-id",
"vcap.application.cf_api:http://my-cloud-controller.com");
this.context.refresh();
return this.context.getBean("cloudFoundryEndpointHandlerMapping",
CloudFoundryEndpointHandlerMapping.class);
}
}

@ -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.autoconfigure.security;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
/**
* Customizer that can be implemented by beans to configure paths that need to be ignored
* by Spring Boot's default Spring Security configuration.
*
* @author Madhura Bhave
* @since 1.5.0
*/
public interface IgnoredRequestCustomizer {
/**
* Customize the provided {@link IgnoredRequestConfigurer}.
* @param configurer the configurer to customize
*/
void customize(IgnoredRequestConfigurer configurer);
}

@ -22,7 +22,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -41,6 +41,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
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.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@ -48,7 +49,9 @@ import org.springframework.security.config.annotation.web.configurers.HeadersCon
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.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -94,8 +97,17 @@ public class SpringBootWebSecurityConfiguration {
@Bean
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter() {
return new IgnoredPathsWebSecurityConfigurerAdapter();
public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter(
List<IgnoredRequestCustomizer> customizers) {
return new IgnoredPathsWebSecurityConfigurerAdapter(customizers);
}
@Bean
public IgnoredRequestCustomizer defaultIgnoredRequestsCustomizer(
ServerProperties server, SecurityProperties security,
ObjectProvider<ErrorController> errorController) {
return new DefaultIgnoredRequestCustomizer(server, security,
errorController.getIfAvailable());
}
public static void configureHeaders(HeadersConfigurer<?> configurer,
@ -146,14 +158,12 @@ public class SpringBootWebSecurityConfiguration {
private static class IgnoredPathsWebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
@Autowired(required = false)
private ErrorController errorController;
private final List<IgnoredRequestCustomizer> customizers;
@Autowired
private SecurityProperties security;
@Autowired
private ServerProperties server;
IgnoredPathsWebSecurityConfigurerAdapter(
List<IgnoredRequestCustomizer> customizers) {
this.customizers = customizers;
}
@Override
public void configure(WebSecurity builder) throws Exception {
@ -161,14 +171,55 @@ public class SpringBootWebSecurityConfiguration {
@Override
public void init(WebSecurity builder) throws Exception {
for (IgnoredRequestCustomizer customizer : this.customizers) {
customizer.customize(builder.ignoring());
}
}
}
private class DefaultIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
private final ServerProperties server;
private final SecurityProperties security;
private final ErrorController errorController;
DefaultIgnoredRequestCustomizer(ServerProperties server,
SecurityProperties security, ErrorController errorController) {
this.server = server;
this.security = security;
this.errorController = errorController;
}
@Override
public void customize(IgnoredRequestConfigurer configurer) {
List<String> ignored = getIgnored(this.security);
if (this.errorController != null) {
ignored.add(normalizePath(this.errorController.getErrorPath()));
}
String[] paths = this.server.getPathsArray(ignored);
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
if (!ObjectUtils.isEmpty(paths)) {
builder.ignoring().antMatchers(paths);
for (String pattern : paths) {
matchers.add(new AntPathRequestMatcher(pattern, null));
}
}
if (!matchers.isEmpty()) {
configurer.requestMatchers(new OrRequestMatcher(matchers));
}
}
private List<String> getIgnored(SecurityProperties security) {
List<String> ignored = new ArrayList<String>(security.getIgnored());
if (ignored.isEmpty()) {
ignored.addAll(DEFAULT_IGNORED);
}
else if (ignored.contains("none")) {
ignored.remove("none");
}
return ignored;
}
private String normalizePath(String errorPath) {

@ -90,9 +90,9 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
// 5 for static resources and one for the rest
// 1 for static resources and one for the rest
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
.hasSize(6);
.hasSize(2);
}
@Test

@ -21,7 +21,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import javax.servlet.Filter;
@ -84,13 +83,6 @@ public class SpringBootWebSecurityConfigurationTests {
}
}
@Test
public void testDefaultIgnores() {
List<String> ignored = SpringBootWebSecurityConfiguration
.getIgnored(new SecurityProperties());
assertThat(ignored).contains("/css/**");
}
@Test
public void testWebConfigurationOverrideGlobalAuthentication() throws Exception {
this.context = SpringApplication.run(TestWebConfiguration.class,

Loading…
Cancel
Save