diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java index 7ceff512875..a1c4a600fe9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.security.reactive; +import reactor.core.publisher.Mono; + import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; @@ -28,10 +30,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.cors.reactive.PreFlightRequestHandler; @@ -50,7 +56,8 @@ import static org.springframework.security.config.Customizer.withDefaults; @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class, - ReactiveOAuth2ResourceServerAutoConfiguration.class }) + ReactiveOAuth2ResourceServerAutoConfiguration.class, + ReactiveUserDetailsServiceAutoConfiguration.class }) @ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class }) @ConditionalOnMissingBean({ SecurityWebFilterChain.class, WebFilterChainProxy.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @@ -69,4 +76,10 @@ public class ReactiveManagementWebSecurityAutoConfiguration { return http.build(); } + @Bean + @ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class }) + ReactiveAuthenticationManager denyAllAuthenticationManager() { + return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName())); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index e41082b893a..66b39ded5a7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -71,17 +71,26 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class)) - .withUserConfiguration(UserDetailsServiceConfiguration.class); + ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class)); @Test void permitAllForHealth() { - this.contextRunner.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull()); } @Test void securesEverythingElse() { + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> { + assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); + assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); + }); + } + + @Test + void noExistingAuthenticationManagerOrUserDetailsService() { this.contextRunner.run((context) -> { + assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull(); assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm="); assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm="); }); @@ -89,10 +98,12 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { @Test void usesMatchersBasedOffConfiguredActuatorBasePath() { - this.contextRunner.withPropertyValues("management.endpoints.web.base-path=/").run((context) -> { - assertThat(getAuthenticateHeader(context, "/health")).isNull(); - assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm="); - }); + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) + .withPropertyValues("management.endpoints.web.base-path=/") + .run((context) -> { + assertThat(getAuthenticateHeader(context, "/health")).isNull(); + assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm="); + }); } @Test @@ -180,6 +191,11 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { return http.build(); } + @Bean + ReactiveAuthenticationManager authenticationManager() { + return mock(ReactiveAuthenticationManager.class); + } + } @Configuration(proxyBeanMethods = false)