diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfiguration.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfiguration.java index 7dc9075528b..689219b3144 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfiguration.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.util.ClassUtils; import org.springframework.web.cors.reactive.PreFlightRequestHandler; import org.springframework.web.cors.reactive.PreFlightRequestWebFilter; @@ -63,7 +64,10 @@ public final class ReactiveManagementWebSecurityAutoConfiguration { @Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, PreFlightRequestHandler handler) { http.authorizeExchange((exchanges) -> { - exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { + exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + } exchanges.anyExchange().authenticated(); }); PreFlightRequestWebFilter filter = new PreFlightRequestWebFilter(handler); diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfiguration.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfiguration.java index 1f019b6d864..028c16a1767 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfiguration.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfiguration.java @@ -59,13 +59,13 @@ public final class ManagementWebSecurityAutoConfiguration { @Bean @Order(SecurityFilterProperties.BASIC_AUTH_ORDER) SecurityFilterChain managementSecurityFilterChain(Environment environment, HttpSecurity http) { - if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", - getClass().getClassLoader())) { - http.authorizeHttpRequests((requests) -> { + http.authorizeHttpRequests((requests) -> { + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); - requests.anyRequest().authenticated(); - }); - } + } + requests.anyRequest().authenticated(); + }); if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) { http.cors(withDefaults()); } diff --git a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index d3966a91ff8..4c758701505 100644 --- a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -36,6 +36,7 @@ import org.springframework.boot.health.autoconfigure.registry.HealthContributorR import org.springframework.boot.security.autoconfigure.web.reactive.ReactiveWebSecurityAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -68,22 +69,26 @@ import static org.springframework.security.config.Customizer.withDefaults; class ReactiveManagementWebSecurityAutoConfigurationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, - HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(InfoEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ReactiveWebSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class)); @Test void permitAllForHealth() { - this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); } @Test void withAdditionalPathsOnSamePort() { - this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .withPropertyValues("management.endpoint.health.group.test1.include=*", "management.endpoint.health.group.test2.include=*", "management.endpoint.health.group.test1.additional-path=server:/check1", @@ -97,7 +102,10 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { @Test void withAdditionalPathsOnDifferentPort() { - this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .withPropertyValues("management.endpoint.health.group.test1.include=*", "management.endpoint.health.group.test2.include=*", "management.endpoint.health.group.test1.additional-path=server:/check1", @@ -112,6 +120,19 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { @Test void securesEverythingElse() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) + .run((context) -> { + assertThat(getRequiredAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); + assertThat(getRequiredAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); + }); + } + + @Test + @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") + void securesEverythingElseWhenHealthIsAbsent() { this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> { assertThat(getRequiredAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); assertThat(getRequiredAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); @@ -120,16 +141,22 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { @Test void noExistingAuthenticationManagerOrUserDetailsService() { - this.contextRunner.run((context) -> { - assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull(); - assertThat(getRequiredAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); - assertThat(getRequiredAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .run((context) -> { + assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull(); + assertThat(getRequiredAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); + assertThat(getRequiredAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); + }); } @Test void usesMatchersBasedOffConfiguredActuatorBasePath() { - this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .withPropertyValues("management.endpoints.web.base-path=/") .run((context) -> { assertThat(getAuthenticateHeader(context, "/health")).isNull(); @@ -139,18 +166,26 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { @Test void backsOffIfCustomSecurityIsAdded() { - this.contextRunner.withUserConfiguration(CustomSecurityConfiguration.class).run((context) -> { - assertThat(getRequiredLocationHeader(context, "/actuator/health").toString()).contains("/login"); - assertThat(getLocationHeader(context, "/foo")).isNull(); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(CustomSecurityConfiguration.class) + .run((context) -> { + assertThat(getRequiredLocationHeader(context, "/actuator/health").toString()).contains("/login"); + assertThat(getLocationHeader(context, "/foo")).isNull(); + }); } @Test void backsOffWhenWebFilterChainProxyBeanPresent() { - this.contextRunner.withUserConfiguration(WebFilterChainProxyConfiguration.class).run((context) -> { - assertThat(getRequiredLocationHeader(context, "/actuator/health").toString()).contains("/login"); - assertThat(getRequiredLocationHeader(context, "/foo").toString()).contains("/login"); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(WebFilterChainProxyConfiguration.class) + .run((context) -> { + assertThat(getRequiredLocationHeader(context, "/actuator/health").toString()).contains("/login"); + assertThat(getRequiredLocationHeader(context, "/foo").toString()).contains("/login"); + }); } private @Nullable List getAuthenticateHeader(AssertableReactiveWebApplicationContext context, String path) { diff --git a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfigurationTests.java b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfigurationTests.java index 719f5142c78..1e29831a862 100644 --- a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfigurationTests.java +++ b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/ManagementWebSecurityAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSec import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.context.WebServerApplicationContext; @@ -71,12 +72,10 @@ class ManagementWebSecurityAutoConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(contextSupplier(), WebServerApplicationContext.class) - .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, - HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, - EndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, WebEndpointAutoConfiguration.class, - SecurityAutoConfiguration.class, ServletWebSecurityAutoConfiguration.class, - ManagementWebSecurityAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(InfoEndpointAutoConfiguration.class, + EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, + WebMvcAutoConfiguration.class, WebEndpointAutoConfiguration.class, SecurityAutoConfiguration.class, + ServletWebSecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); private static Supplier contextSupplier() { return WebApplicationContextRunner.withMockServletContext(MockWebServerApplicationContext::new); @@ -84,15 +83,32 @@ class ManagementWebSecurityAutoConfigurationTests { @Test void permitAllForHealth() { - this.contextRunner.run((context) -> { - assertThat(context).hasBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN); - HttpStatus status = getResponseStatus(context, "/actuator/health"); - assertThat(status).isEqualTo(HttpStatus.OK); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .run((context) -> { + assertThat(context).hasBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN); + HttpStatus status = getResponseStatus(context, "/actuator/health"); + assertThat(status).isEqualTo(HttpStatus.OK); + }); } @Test void securesEverythingElse() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .run((context) -> { + HttpStatus status = getResponseStatus(context, "/actuator"); + assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED); + status = getResponseStatus(context, "/foo"); + assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED); + }); + } + + @Test + @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") + void securesEverythingElseWhenHealthIsAbsent() { this.contextRunner.run((context) -> { HttpStatus status = getResponseStatus(context, "/actuator"); assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED); @@ -112,20 +128,28 @@ class ManagementWebSecurityAutoConfigurationTests { @Test void usesMatchersBasedOffConfiguredActuatorBasePath() { - this.contextRunner.withPropertyValues("management.endpoints.web.base-path=/").run((context) -> { - HttpStatus status = getResponseStatus(context, "/health"); - assertThat(status).isEqualTo(HttpStatus.OK); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withPropertyValues("management.endpoints.web.base-path=/") + .run((context) -> { + HttpStatus status = getResponseStatus(context, "/health"); + assertThat(status).isEqualTo(HttpStatus.OK); + }); } @Test void backOffIfCustomSecurityIsAdded() { - this.contextRunner.withUserConfiguration(CustomSecurityConfiguration.class).run((context) -> { - HttpStatus status = getResponseStatus(context, "/actuator/health"); - assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED); - status = getResponseStatus(context, "/foo"); - assertThat(status).isEqualTo(HttpStatus.OK); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withUserConfiguration(CustomSecurityConfiguration.class) + .run((context) -> { + HttpStatus status = getResponseStatus(context, "/actuator/health"); + assertThat(status).isEqualTo(HttpStatus.UNAUTHORIZED); + status = getResponseStatus(context, "/foo"); + assertThat(status).isEqualTo(HttpStatus.OK); + }); } @Test @@ -155,6 +179,8 @@ class ManagementWebSecurityAutoConfigurationTests { @Test void withAdditionalPathsOnSamePort() { this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) .withPropertyValues("management.endpoint.health.group.test1.include=*", "management.endpoint.health.group.test2.include=*", "management.endpoint.health.group.test1.additional-path=server:/check1", @@ -168,10 +194,14 @@ class ManagementWebSecurityAutoConfigurationTests { @Test void withAdditionalPathsOnDifferentPort() { - this.contextRunner.withPropertyValues("management.endpoint.health.group.test1.include=*", - "management.endpoint.health.group.test2.include=*", - "management.endpoint.health.group.test1.additional-path=server:/check1", - "management.endpoint.health.group.test2.additional-path=management:/check2", "management.server.port=0") + this.contextRunner + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) + .withPropertyValues("management.endpoint.health.group.test1.include=*", + "management.endpoint.health.group.test2.include=*", + "management.endpoint.health.group.test1.additional-path=server:/check1", + "management.endpoint.health.group.test2.additional-path=management:/check2", + "management.server.port=0") .run((context) -> { assertThat(getResponseStatus(context, "/check1")).isEqualTo(HttpStatus.OK); assertThat(getResponseStatus(context, "/check2")).isEqualTo(HttpStatus.UNAUTHORIZED);