Skip to content

Commit 6ec56da

Browse files
committed
Ensure that reactive actuator security has an auth manager
This is a follow-on from afad358 and ensures that the auto-configured security for Actuator in a WebFlux app has an authentication manager to back its use of HTTP basic and form login. Fixes gh-39069
1 parent a48e2d3 commit 6ec56da

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

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

19+
import reactor.core.publisher.Mono;
20+
1921
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
2022
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
2123
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
@@ -28,10 +30,14 @@
2830
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
2931
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
3032
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
3134
import org.springframework.context.annotation.Bean;
35+
import org.springframework.security.authentication.ReactiveAuthenticationManager;
3236
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
3337
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
3438
import org.springframework.security.config.web.server.ServerHttpSecurity;
39+
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
40+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
3541
import org.springframework.security.web.server.SecurityWebFilterChain;
3642
import org.springframework.security.web.server.WebFilterChainProxy;
3743
import org.springframework.web.cors.reactive.PreFlightRequestHandler;
@@ -50,7 +56,8 @@
5056
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class,
5157
after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
5258
WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class,
53-
ReactiveOAuth2ResourceServerAutoConfiguration.class })
59+
ReactiveOAuth2ResourceServerAutoConfiguration.class,
60+
ReactiveUserDetailsServiceAutoConfiguration.class })
5461
@ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class })
5562
@ConditionalOnMissingBean({ SecurityWebFilterChain.class, WebFilterChainProxy.class })
5663
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@@ -69,4 +76,10 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
6976
return http.build();
7077
}
7178

79+
@Bean
80+
@ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class })
81+
ReactiveAuthenticationManager denyAllAuthenticationManager() {
82+
return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName()));
83+
}
84+
7285
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -71,28 +71,39 @@ class ReactiveManagementWebSecurityAutoConfigurationTests {
7171
HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
7272
WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class,
7373
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
74-
ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class))
75-
.withUserConfiguration(UserDetailsServiceConfiguration.class);
74+
ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class));
7675

7776
@Test
7877
void permitAllForHealth() {
79-
this.contextRunner.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull());
78+
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class)
79+
.run((context) -> assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull());
8080
}
8181

8282
@Test
8383
void securesEverythingElse() {
84+
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> {
85+
assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm=");
86+
assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm=");
87+
});
88+
}
89+
90+
@Test
91+
void noExistingAuthenticationManagerOrUserDetailsService() {
8492
this.contextRunner.run((context) -> {
93+
assertThat(getAuthenticateHeader(context, "/actuator/health")).isNull();
8594
assertThat(getAuthenticateHeader(context, "/actuator").get(0)).contains("Basic realm=");
8695
assertThat(getAuthenticateHeader(context, "/foo").toString()).contains("Basic realm=");
8796
});
8897
}
8998

9099
@Test
91100
void usesMatchersBasedOffConfiguredActuatorBasePath() {
92-
this.contextRunner.withPropertyValues("management.endpoints.web.base-path=/").run((context) -> {
93-
assertThat(getAuthenticateHeader(context, "/health")).isNull();
94-
assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm=");
95-
});
101+
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class)
102+
.withPropertyValues("management.endpoints.web.base-path=/")
103+
.run((context) -> {
104+
assertThat(getAuthenticateHeader(context, "/health")).isNull();
105+
assertThat(getAuthenticateHeader(context, "/foo").get(0)).contains("Basic realm=");
106+
});
96107
}
97108

98109
@Test
@@ -180,6 +191,11 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
180191
return http.build();
181192
}
182193

194+
@Bean
195+
ReactiveAuthenticationManager authenticationManager() {
196+
return mock(ReactiveAuthenticationManager.class);
197+
}
198+
183199
}
184200

185201
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)