Skip to content

Commit 95be208

Browse files
committed
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 spring-projectsgh-7106
1 parent d09aafa commit 95be208

File tree

7 files changed

+182
-113
lines changed

7 files changed

+182
-113
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java

Lines changed: 30 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import javax.servlet.http.HttpServletRequest;
2727

2828
import org.springframework.beans.factory.ObjectProvider;
29-
import org.springframework.beans.factory.annotation.Autowired;
3029
import org.springframework.boot.actuate.endpoint.Endpoint;
3130
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
3231
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
@@ -42,11 +41,11 @@
4241
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
4342
import org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration;
4443
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
44+
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
4545
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
4646
import org.springframework.boot.autoconfigure.security.SecurityPrerequisite;
4747
import org.springframework.boot.autoconfigure.security.SecurityProperties;
4848
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
49-
import org.springframework.boot.autoconfigure.web.ErrorController;
5049
import org.springframework.boot.autoconfigure.web.ServerProperties;
5150
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5251
import org.springframework.context.ApplicationContext;
@@ -56,9 +55,7 @@
5655
import org.springframework.context.annotation.Configuration;
5756
import org.springframework.core.annotation.Order;
5857
import org.springframework.core.type.AnnotatedTypeMetadata;
59-
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
6058
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
61-
import org.springframework.security.config.annotation.web.builders.WebSecurity;
6259
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
6360
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
6461
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
@@ -72,7 +69,6 @@
7269
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
7370
import org.springframework.security.web.util.matcher.OrRequestMatcher;
7471
import org.springframework.security.web.util.matcher.RequestMatcher;
75-
import org.springframework.util.ObjectUtils;
7672
import org.springframework.util.StringUtils;
7773

7874
/**
@@ -102,9 +98,34 @@ public class ManagementWebSecurityAutoConfiguration {
10298
AnyRequestMatcher.INSTANCE);
10399

104100
@Bean
105-
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
106-
public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter() {
107-
return new IgnoredPathsWebSecurityConfigurerAdapter();
101+
public IgnoredRequestCustomizer managementIgnoredRequestCustomizer(
102+
ManagementServerProperties management,
103+
ObjectProvider<ManagementContextResolver> contextResolverProvider) {
104+
return new ManagementIgnoredRequestCustomizer(management,
105+
contextResolverProvider.getIfAvailable());
106+
}
107+
108+
private class ManagementIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
109+
110+
private final ManagementServerProperties management;
111+
112+
private final ManagementContextResolver contextResolver;
113+
114+
ManagementIgnoredRequestCustomizer(ManagementServerProperties management,
115+
ManagementContextResolver contextResolver) {
116+
this.management = management;
117+
this.contextResolver = contextResolver;
118+
}
119+
120+
@Override
121+
public void customize(IgnoredRequestConfigurer configurer) {
122+
if (!this.management.getSecurity().isEnabled()) {
123+
RequestMatcher requestMatcher = LazyEndpointPathRequestMatcher
124+
.getRequestMatcher(this.contextResolver);
125+
configurer.requestMatchers(requestMatcher);
126+
}
127+
128+
}
108129
}
109130

110131
@Configuration
@@ -132,80 +153,6 @@ public void init() {
132153

133154
}
134155

135-
// Get the ignored paths in early
136-
@Order(SecurityProperties.IGNORED_ORDER + 1)
137-
private static class IgnoredPathsWebSecurityConfigurerAdapter
138-
implements WebSecurityConfigurer<WebSecurity> {
139-
140-
@Autowired(required = false)
141-
private ErrorController errorController;
142-
143-
@Autowired
144-
private SecurityProperties security;
145-
146-
@Autowired
147-
private ManagementServerProperties management;
148-
149-
@Autowired(required = false)
150-
private ManagementContextResolver contextResolver;
151-
152-
@Autowired(required = false)
153-
private ServerProperties server;
154-
155-
@Override
156-
public void configure(WebSecurity builder) throws Exception {
157-
}
158-
159-
@Override
160-
public void init(WebSecurity builder) throws Exception {
161-
if (this.server == null) {
162-
return;
163-
}
164-
IgnoredRequestConfigurer ignoring = builder.ignoring();
165-
// The ignores are not cumulative, so to prevent overwriting the defaults
166-
// we add them back.
167-
Set<String> ignored = new LinkedHashSet<String>(
168-
SpringBootWebSecurityConfiguration.getIgnored(this.security));
169-
if (ignored.contains("none")) {
170-
ignored.remove("none");
171-
}
172-
if (this.errorController != null) {
173-
ignored.add(normalizePath(this.errorController.getErrorPath()));
174-
}
175-
RequestMatcher requestMatcher = getRequestMatcher();
176-
String[] paths = this.server.getPathsArray(ignored);
177-
if (!ObjectUtils.isEmpty(paths)) {
178-
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
179-
for (String pattern : paths) {
180-
matchers.add(new AntPathRequestMatcher(pattern, null));
181-
}
182-
if (requestMatcher != null) {
183-
matchers.add(requestMatcher);
184-
}
185-
requestMatcher = new OrRequestMatcher(matchers);
186-
}
187-
if (requestMatcher != null) {
188-
ignoring.requestMatchers(requestMatcher);
189-
}
190-
}
191-
192-
private RequestMatcher getRequestMatcher() {
193-
if (this.management.getSecurity().isEnabled()) {
194-
return null;
195-
}
196-
return LazyEndpointPathRequestMatcher.getRequestMatcher(this.contextResolver);
197-
}
198-
199-
private String normalizePath(String errorPath) {
200-
String result = StringUtils.cleanPath(errorPath);
201-
if (!result.startsWith("/")) {
202-
result = "/" + result;
203-
}
204-
return result;
205-
}
206-
207-
}
208-
209156
@Configuration
210157
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
211158
@Conditional(WebSecurityEnablerCondition.class)
@@ -310,9 +257,7 @@ private void configurePermittedRequests(
310257
// Permit access to the non-sensitive endpoints
311258
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
312259
this.contextResolver, EndpointPaths.NON_SENSITIVE)).permitAll();
313-
// Restrict the rest to the configured roles
314-
List<String> roles = this.management.getSecurity().getRoles();
315-
requests.anyRequest().hasAnyRole(roles.toArray(new String[roles.size()]));
260+
requests.anyRequest().authenticated();
316261
}
317262

318263
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
31+
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
3132
import org.springframework.boot.cloud.CloudPlatform;
3233
import org.springframework.boot.web.client.RestTemplateBuilder;
3334
import org.springframework.context.annotation.Bean;
3435
import org.springframework.context.annotation.Configuration;
3536
import org.springframework.core.env.Environment;
3637
import org.springframework.http.HttpMethod;
38+
import org.springframework.security.config.annotation.web.builders.WebSecurity;
39+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3740
import org.springframework.web.cors.CorsConfiguration;
3841
import org.springframework.web.servlet.HandlerInterceptor;
3942

@@ -95,4 +98,20 @@ private CorsConfiguration getCorsConfiguration() {
9598
return corsConfiguration;
9699
}
97100

101+
@Bean
102+
public IgnoredRequestCustomizer cloudFoundryIgnoredRequestCustomizer() {
103+
return new CloudFoundryIgnoredRequestCustomizer();
104+
}
105+
106+
private class CloudFoundryIgnoredRequestCustomizer
107+
implements IgnoredRequestCustomizer {
108+
109+
@Override
110+
public void customize(WebSecurity.IgnoredRequestConfigurer configurer) {
111+
configurer.requestMatchers(
112+
new AntPathRequestMatcher("/cloudfoundryapplication/**"));
113+
}
114+
115+
}
116+
98117
}

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.junit.After;
2222
import org.junit.Before;
2323
import org.junit.Test;
24+
import org.mockito.ArgumentCaptor;
2425

2526
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
2627
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
@@ -29,18 +30,24 @@
2930
import org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration;
3031
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
3132
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
3234
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
3335
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
3436
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
3537
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
3638
import org.springframework.boot.test.util.EnvironmentTestUtils;
3739
import org.springframework.http.HttpMethod;
40+
import org.springframework.mock.web.MockHttpServletRequest;
3841
import org.springframework.mock.web.MockServletContext;
42+
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
43+
import org.springframework.security.web.util.matcher.RequestMatcher;
3944
import org.springframework.test.util.ReflectionTestUtils;
4045
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
4146
import org.springframework.web.cors.CorsConfiguration;
4247

4348
import static org.assertj.core.api.Assertions.assertThat;
49+
import static org.mockito.Mockito.mock;
50+
import static org.mockito.Mockito.verify;
4451

4552
/**
4653
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
@@ -116,24 +123,34 @@ public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent()
116123
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
117124
"vcap.application.application_id:my-app-id");
118125
this.context.refresh();
119-
CloudFoundryEndpointHandlerMapping handlerMapping1 = this.context.getBean(
126+
CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean(
120127
"cloudFoundryEndpointHandlerMapping",
121128
CloudFoundryEndpointHandlerMapping.class);
122-
CloudFoundryEndpointHandlerMapping handlerMapping = handlerMapping1;
123129
Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping,
124130
"securityInterceptor");
125131
Object interceptorSecurityService = ReflectionTestUtils
126132
.getField(securityInterceptor, "cloudFoundrySecurityService");
127133
assertThat(interceptorSecurityService).isNull();
128134
}
129135

130-
private CloudFoundryEndpointHandlerMapping getHandlerMapping() {
136+
@Test
137+
public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception {
131138
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
132-
"vcap.application.application_id:my-app-id",
133-
"vcap.application.cf_api:http://my-cloud-controller.com");
139+
"vcap.application.application_id:my-app-id");
134140
this.context.refresh();
135-
return this.context.getBean("cloudFoundryEndpointHandlerMapping",
136-
CloudFoundryEndpointHandlerMapping.class);
141+
IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context
142+
.getBean("cloudFoundryIgnoredRequestCustomizer");
143+
IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class);
144+
customizer.customize(configurer);
145+
ArgumentCaptor<RequestMatcher> requestMatcher = ArgumentCaptor
146+
.forClass(RequestMatcher.class);
147+
verify(configurer).requestMatchers(requestMatcher.capture());
148+
RequestMatcher matcher = requestMatcher.getValue();
149+
MockHttpServletRequest request = new MockHttpServletRequest();
150+
request.setServletPath("/cloudfoundryapplication/my-path");
151+
assertThat(matcher.matches(request)).isTrue();
152+
request.setServletPath("/some-other-path");
153+
assertThat(matcher.matches(request)).isFalse();
137154
}
138155

139156
@Test
@@ -152,4 +169,13 @@ public void cloudFoundryManagementEndpointsDisabled() throws Exception {
152169
.isFalse();
153170
}
154171

172+
private CloudFoundryEndpointHandlerMapping getHandlerMapping() {
173+
EnvironmentTestUtils.addEnvironment(this.context, "VCAP_APPLICATION:---",
174+
"vcap.application.application_id:my-app-id",
175+
"vcap.application.cf_api:http://my-cloud-controller.com");
176+
this.context.refresh();
177+
return this.context.getBean("cloudFoundryEndpointHandlerMapping",
178+
CloudFoundryEndpointHandlerMapping.class);
179+
}
180+
155181
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security;
18+
19+
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
20+
21+
/**
22+
* Customizer that can be implemented by beans to configure paths that need to be ignored
23+
* by Spring Boot's default Spring Security configuration.
24+
*
25+
* @author Madhura Bhave
26+
* @since 1.5.0
27+
*/
28+
public interface IgnoredRequestCustomizer {
29+
30+
/**
31+
* Customize the provided {@link IgnoredRequestConfigurer}.
32+
* @param configurer the configurer to customize
33+
*/
34+
void customize(IgnoredRequestConfigurer configurer);
35+
36+
}

0 commit comments

Comments
 (0)