Skip to content

Commit ad5cb8a

Browse files
committed
Skip MvcSecurityInterceptor if Spring Security present
If Spring Security is on the classpath, the role check can be done as part of the ManagementWebSecurityConfigurerAdapter. Fixes gh-8255
1 parent 30eb9c6 commit ad5cb8a

File tree

8 files changed

+184
-39
lines changed

8 files changed

+184
-39
lines changed

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.springframework.context.annotation.Conditional;
5555
import org.springframework.core.env.Environment;
5656
import org.springframework.core.type.AnnotatedTypeMetadata;
57+
import org.springframework.util.ClassUtils;
5758
import org.springframework.util.CollectionUtils;
5859
import org.springframework.util.StringUtils;
5960
import org.springframework.web.cors.CorsConfiguration;
@@ -102,16 +103,29 @@ public EndpointHandlerMapping endpointHandlerMapping() {
102103
EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
103104
corsConfiguration);
104105
mapping.setPrefix(this.managementServerProperties.getContextPath());
105-
MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor(
106-
this.managementServerProperties.getSecurity().isEnabled(),
107-
this.managementServerProperties.getSecurity().getRoles());
108-
mapping.setSecurityInterceptor(securityInterceptor);
106+
if (isSecurityInterceptorRequired()) {
107+
MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor(
108+
this.managementServerProperties.getSecurity().getRoles());
109+
mapping.setSecurityInterceptor(securityInterceptor);
110+
}
111+
109112
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
110113
customizer.customize(mapping);
111114
}
112115
return mapping;
113116
}
114117

118+
private boolean isSecurityInterceptorRequired() {
119+
return this.managementServerProperties.getSecurity().isEnabled()
120+
&& !isSpringSecurityAvailable();
121+
}
122+
123+
private boolean isSpringSecurityAvailable() {
124+
return ClassUtils.isPresent(
125+
"org.springframework.security.config.annotation.web.WebSecurityConfigurer",
126+
getClass().getClassLoader());
127+
}
128+
115129
private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) {
116130
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
117131
return null;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,10 @@ private AuthenticationEntryPoint entryPoint() {
257257

258258
private void configurePermittedRequests(
259259
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests) {
260+
List<String> roles = this.management.getSecurity().getRoles();
260261
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
261-
this.contextResolver, EndpointPaths.SENSITIVE)).authenticated();
262+
this.contextResolver, EndpointPaths.SENSITIVE))
263+
.hasAnyRole(roles.toArray(new String[roles.size()]));
262264
// Permit access to the non-sensitive endpoints
263265
requests.requestMatchers(new LazyEndpointPathRequestMatcher(
264266
this.contextResolver, EndpointPaths.NON_SENSITIVE)).permitAll();

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,18 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {
4343
private static final Log logger = LogFactory
4444
.getLog(MvcEndpointSecurityInterceptor.class);
4545

46-
private final boolean secure;
47-
4846
private final List<String> roles;
4947

5048
private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean();
5149

52-
public MvcEndpointSecurityInterceptor(boolean secure, List<String> roles) {
53-
this.secure = secure;
50+
public MvcEndpointSecurityInterceptor(List<String> roles) {
5451
this.roles = roles;
5552
}
5653

5754
@Override
5855
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
5956
Object handler) throws Exception {
60-
if (CorsUtils.isPreFlightRequest(request) || !this.secure) {
57+
if (CorsUtils.isPreFlightRequest(request)) {
6158
return true;
6259
}
6360
HandlerMethod handlerMethod = (HandlerMethod) handler;

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfigurationTests.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure;
1818

19-
import java.util.List;
20-
2119
import org.junit.After;
2220
import org.junit.Before;
2321
import org.junit.Test;
@@ -30,7 +28,6 @@
3028
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
3129
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
3230
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
33-
import org.springframework.boot.test.util.EnvironmentTestUtils;
3431
import org.springframework.mock.web.MockServletContext;
3532
import org.springframework.test.util.ReflectionTestUtils;
3633
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -58,6 +55,8 @@ public void setup() {
5855
PropertyPlaceholderAutoConfiguration.class,
5956
WebClientAutoConfiguration.class,
6057
EndpointWebMvcManagementContextConfiguration.class);
58+
this.context.refresh();
59+
6160
}
6261

6362
@After
@@ -68,25 +67,13 @@ public void close() {
6867
}
6968

7069
@Test
71-
public void endpointHandlerMapping() throws Exception {
72-
EnvironmentTestUtils.addEnvironment(this.context,
73-
"management.security.enabled=false",
74-
"management.security.roles=my-role,your-role");
75-
this.context.refresh();
70+
public void endpointHandlerMappingShouldNotHaveSecurityInterceptor() throws Exception {
7671
EndpointHandlerMapping mapping = this.context.getBean("endpointHandlerMapping",
7772
EndpointHandlerMapping.class);
7873
assertThat(mapping.getPrefix()).isEmpty();
7974
MvcEndpointSecurityInterceptor securityInterceptor = (MvcEndpointSecurityInterceptor) ReflectionTestUtils
8075
.getField(mapping, "securityInterceptor");
81-
Object secure = ReflectionTestUtils.getField(securityInterceptor, "secure");
82-
List<String> roles = getRoles(securityInterceptor);
83-
assertThat(secure).isEqualTo(false);
84-
assertThat(roles).containsExactly("my-role", "your-role");
85-
}
86-
87-
@SuppressWarnings("unchecked")
88-
private List<String> getRoles(MvcEndpointSecurityInterceptor securityInterceptor) {
89-
return (List<String>) ReflectionTestUtils.getField(securityInterceptor, "roles");
76+
assertThat(securityInterceptor).isNull();
9077
}
9178

9279
}

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfigurationTests.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.actuate.autoconfigure;
1818

1919
import java.util.Collection;
20-
import java.util.Collections;
2120

2221
import org.hamcrest.Matchers;
2322
import org.junit.After;
@@ -26,7 +25,6 @@
2625
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
2726
import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint;
2827
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
29-
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor;
3028
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
3129
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
3230
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
@@ -144,8 +142,6 @@ protected static class EndpointsConfig extends Config {
144142
public EndpointHandlerMapping endpointHandlerMapping(
145143
Collection<? extends MvcEndpoint> endpoints) {
146144
EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints);
147-
mapping.setSecurityInterceptor(new MvcEndpointSecurityInterceptor(false,
148-
Collections.<String>emptyList()));
149145
return mapping;
150146
}
151147

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfigurationTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
5656
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
5757
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
58+
import org.springframework.util.Base64Utils;
5859
import org.springframework.util.StringUtils;
5960
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6061

@@ -254,6 +255,57 @@ public void testMarkAllEndpointsSensitive() throws Exception {
254255
.andExpect(status().isUnauthorized());
255256
}
256257

258+
@Test
259+
public void sensitiveEndpointWithRightRoleShouldReturnOk() throws Exception {
260+
this.context = new AnnotationConfigWebApplicationContext();
261+
this.context.setServletContext(new MockServletContext());
262+
this.context.register(AuthenticationConfig.class, WebConfiguration.class);
263+
EnvironmentTestUtils.addEnvironment(this.context, "management.security.roles:USER");
264+
this.context.refresh();
265+
266+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
267+
.apply(springSecurity())
268+
.build();
269+
270+
String authorizationHeader = Base64Utils.encodeToString("user:password".getBytes());
271+
mockMvc //
272+
.perform(get("/env").header("authorization", "Basic " + authorizationHeader))
273+
.andExpect(status().isOk());
274+
}
275+
276+
@Test
277+
public void sensitiveEndpointWithRightRoleShouldReturnForbidden() throws Exception {
278+
this.context = new AnnotationConfigWebApplicationContext();
279+
this.context.setServletContext(new MockServletContext());
280+
this.context.register(AuthenticationConfig.class, WebConfiguration.class);
281+
this.context.refresh();
282+
283+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
284+
.apply(springSecurity())
285+
.build();
286+
287+
String authorizationHeader = Base64Utils.encodeToString("user:password".getBytes());
288+
mockMvc //
289+
.perform(get("/env").header("authorization", "Basic " + authorizationHeader))
290+
.andExpect(status().isForbidden());
291+
}
292+
293+
@Test
294+
public void nonSensitiveEndpointShouldAlwaysReturnOk() throws Exception {
295+
this.context = new AnnotationConfigWebApplicationContext();
296+
this.context.setServletContext(new MockServletContext());
297+
this.context.register(WebConfiguration.class);
298+
this.context.refresh();
299+
300+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
301+
.apply(springSecurity())
302+
.build();
303+
304+
mockMvc //
305+
.perform(get("/health"))
306+
.andExpect(status().isOk());
307+
}
308+
257309
private ResultMatcher springAuthenticateRealmHeader() {
258310
return MockMvcResultMatchers.header().string("www-authenticate",
259311
Matchers.containsString("realm=\"Spring\""));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2012-2017 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.actuate.autoconfigure;
18+
19+
import java.util.List;
20+
21+
import org.junit.After;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
27+
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor;
28+
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
33+
import org.springframework.boot.junit.runner.classpath.ClassPathExclusions;
34+
import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner;
35+
import org.springframework.boot.test.util.EnvironmentTestUtils;
36+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
37+
import org.springframework.test.util.ReflectionTestUtils;
38+
39+
import static org.assertj.core.api.Assertions.assertThat;
40+
41+
/**
42+
* Tests for {@link EndpointWebMvcManagementContextConfiguration} when Spring Security is not available.
43+
*
44+
* @author Madhura Bhave
45+
*/
46+
@RunWith(ModifiedClassPathRunner.class)
47+
@ClassPathExclusions("spring-security-*.jar")
48+
public class NoSpringSecurityEndpointWebMvcManagementContextConfigurationTests {
49+
50+
private AnnotationConfigApplicationContext context;
51+
52+
@Before
53+
public void setup() {
54+
this.context = new AnnotationConfigApplicationContext();
55+
this.context.register(WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
56+
HttpMessageConvertersAutoConfiguration.class,
57+
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
58+
ManagementServerPropertiesAutoConfiguration.class,
59+
PropertyPlaceholderAutoConfiguration.class,
60+
WebClientAutoConfiguration.class,
61+
EndpointWebMvcManagementContextConfiguration.class);
62+
}
63+
64+
@After
65+
public void close() {
66+
if (this.context != null) {
67+
this.context.close();
68+
}
69+
}
70+
71+
@Test
72+
public void endpointHandlerMappingWhenSecurityEnabledShouldHaveSecurityInterceptor() throws Exception {
73+
EnvironmentTestUtils.addEnvironment(this.context,
74+
"management.security.enabled=true",
75+
"management.security.roles=my-role,your-role");
76+
this.context.refresh();
77+
EndpointHandlerMapping mapping = this.context.getBean("endpointHandlerMapping",
78+
EndpointHandlerMapping.class);
79+
MvcEndpointSecurityInterceptor securityInterceptor = (MvcEndpointSecurityInterceptor) ReflectionTestUtils
80+
.getField(mapping, "securityInterceptor");
81+
List<String> roles = getRoles(securityInterceptor);
82+
assertThat(roles).containsExactly("my-role", "your-role");
83+
}
84+
85+
@Test
86+
public void endpointHandlerMappingWhenSecurityDisabledShouldHaveSecurityInterceptor() throws Exception {
87+
EnvironmentTestUtils.addEnvironment(this.context,
88+
"management.security.enabled=false",
89+
"management.security.roles=my-role,your-role");
90+
this.context.refresh();
91+
EndpointHandlerMapping mapping = this.context.getBean("endpointHandlerMapping",
92+
EndpointHandlerMapping.class);
93+
MvcEndpointSecurityInterceptor securityInterceptor = (MvcEndpointSecurityInterceptor) ReflectionTestUtils
94+
.getField(mapping, "securityInterceptor");
95+
assertThat(securityInterceptor).isNull();
96+
}
97+
98+
@SuppressWarnings("unchecked")
99+
private List<String> getRoles(MvcEndpointSecurityInterceptor securityInterceptor) {
100+
return (List<String>) ReflectionTestUtils.getField(securityInterceptor, "roles");
101+
}
102+
103+
}
104+

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class MvcEndpointSecurityInterceptorTests {
6666
@Before
6767
public void setup() throws Exception {
6868
this.roles = Arrays.asList("SUPER_HERO");
69-
this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles);
69+
this.securityInterceptor = new MvcEndpointSecurityInterceptor(this.roles);
7070
this.endpoint = new TestEndpoint("a");
7171
this.mvcEndpoint = new TestMvcEndpoint(this.endpoint);
7272
this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke");
@@ -75,13 +75,6 @@ public void setup() throws Exception {
7575
this.response = mock(HttpServletResponse.class);
7676
}
7777

78-
@Test
79-
public void securityDisabledShouldAllowAccess() throws Exception {
80-
this.securityInterceptor = new MvcEndpointSecurityInterceptor(false, this.roles);
81-
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
82-
this.handlerMethod)).isTrue();
83-
}
84-
8578
@Test
8679
public void endpointNotSensitiveShouldAllowAccess() throws Exception {
8780
this.endpoint.setSensitive(false);

0 commit comments

Comments
 (0)