Merge branch '4.1.x'

Closes gh-51588
This commit is contained in:
Andy Wilkinson
2026-09-04 16:20:05 +01:00
15 changed files with 287 additions and 20 deletions
@@ -19,7 +19,7 @@ package org.springframework.boot.security.oauth2.server.authorization.autoconfig
import java.util.Set;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultServletWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -42,7 +42,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
@ConditionalOnBean({ RegisteredClientRepository.class, AuthorizationServerSettings.class })
class OAuth2AuthorizationServerWebSecurityConfiguration {
@@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultServletWebSecurity;
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -59,7 +59,7 @@ public final class OAuth2ClientWebSecurityAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
static class OAuth2SecurityFilterChainConfiguration {
@Bean
@@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultServletWebSecurity;
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.OAuth2ResourceServerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -45,7 +45,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
@AutoConfiguration(before = { ManagementWebSecurityAutoConfiguration.class, SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class }, after = OAuth2ResourceServerAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
public final class OAuth2ResourceServerWebSecurityAutoConfiguration {
@Bean
@@ -19,17 +19,15 @@ package org.springframework.boot.security.oauth2.server.resource.autoconfigure.w
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.security.autoconfigure.ReactiveUserDetailsServiceAutoConfiguration;
import org.springframework.boot.security.autoconfigure.actuate.web.reactive.ReactiveManagementWebSecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.web.reactive.ConditionalOnDefaultReactiveWebSecurity;
import org.springframework.boot.security.autoconfigure.web.reactive.ReactiveWebSecurityAutoConfiguration;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
@@ -51,8 +49,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
ReactiveUserDetailsServiceAutoConfiguration.class },
after = ReactiveOAuth2ResourceServerAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnDefaultWebSecurity
@ConditionalOnClass({ EnableWebFluxSecurity.class })
@ConditionalOnDefaultReactiveWebSecurity
public final class ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration {
@Bean
@@ -35,6 +35,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -73,6 +74,7 @@ class ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationTests {
@Test
void whenNoReactiveJwtDecoderDoesNotAddFilterChain() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.class);
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(context);
assertThatExceptionOfType(UsernameNotFoundException.class)
.isThrownBy(authenticationManager.authenticate(mock())::block);
@@ -82,6 +84,7 @@ class ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationTests {
@Test
void whenHasReactiveJwtDecoderAddsFilterChain() {
this.contextRunner.withPropertyValues(JWK_SET_URI_PROPERTY).run((context) -> {
assertThat(context).hasSingleBean(ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.class);
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(context);
assertThat(authenticationManager).isInstanceOf(JwtReactiveAuthenticationManager.class);
});
@@ -90,6 +93,7 @@ class ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationTests {
@Test
void whenNoReactiveOpaqueTokenIntrospectorDoesNotAddFilterChain() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.class);
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(context);
assertThatExceptionOfType(UsernameNotFoundException.class)
.isThrownBy(authenticationManager.authenticate(mock())::block);
@@ -104,11 +108,19 @@ class ReactiveOAuth2ResourceServerWebSecurityAutoConfigurationTests {
"spring.security.oauth2.resourceserver.opaquetoken.client-id=test",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=shh")
.run((context) -> {
assertThat(context).hasSingleBean(ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.class);
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(context);
assertThat(authenticationManager).isInstanceOf(OpaqueTokenReactiveAuthenticationManager.class);
});
}
@Test
void backsOffWhenExistingSecurityWebFilterChain() {
this.contextRunner.withBean(SecurityWebFilterChain.class, () -> mock(SecurityWebFilterChain.class))
.run((context) -> assertThat(context)
.doesNotHaveBean(ReactiveOAuth2ResourceServerWebSecurityAutoConfiguration.class));
}
private ReactiveAuthenticationManager getAuthenticationManager(AssertableReactiveWebApplicationContext context) {
AuthenticationWebFilter authenticationWebFilter = getAuthenticationWebFilter(context);
assertThat(authenticationWebFilter).isNotNull();
@@ -17,7 +17,7 @@
package org.springframework.boot.security.saml2.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultServletWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -33,7 +33,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
@ConditionalOnBean(RelyingPartyRegistrationRepository.class)
class Saml2LoginConfiguration {
@@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.health.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultServletWebSecurity;
import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties;
import org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -53,7 +53,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
"org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration" })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ RequestMatcher.class, WebEndpointAutoConfiguration.class })
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
public final class ManagementWebSecurityAutoConfiguration {
@Bean
@@ -0,0 +1,40 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.security.autoconfigure.web.reactive;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional @Conditional} that only matches when reactive web security is
* available and the user has not defined their own configuration.
*
* @author Phillip Webb
* @since 4.1.2
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultReactiveWebSecurityCondition.class)
public @interface ConditionalOnDefaultReactiveWebSecurity {
}
@@ -0,0 +1,50 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.security.autoconfigure.web.reactive;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Condition;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* {@link Condition} for
* {@link ConditionalOnDefaultReactiveWebSecurity @ConditionalOnDefaultReactiveWebSecurity}.
*
* @author Andy Wilkinson
*/
class DefaultReactiveWebSecurityCondition extends AllNestedConditions {
DefaultReactiveWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ Flux.class, SecurityWebFilterChain.class, ServerHttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean(SecurityWebFilterChain.class)
static class Beans {
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.security.autoconfigure.web.servlet;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional @Conditional} that only matches when web security is available and
* the user has not defined their own configuration.
*
* @author Phillip Webb
* @since 4.1.2
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultServletWebSecurityCondition.class)
public @interface ConditionalOnDefaultServletWebSecurity {
}
@@ -30,11 +30,13 @@ import org.springframework.context.annotation.Conditional;
*
* @author Phillip Webb
* @since 4.0.0
* @deprecated since 4.1.2 in favor of {@link ConditionalOnDefaultServletWebSecurity}
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
@Conditional(DefaultServletWebSecurityCondition.class)
@Deprecated(since = "4.1.2", forRemoval = true)
public @interface ConditionalOnDefaultWebSecurity {
}
@@ -25,13 +25,13 @@ import org.springframework.security.web.SecurityFilterChain;
/**
* {@link Condition} for
* {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}.
* {@link ConditionalOnDefaultServletWebSecurity @ConditionalOnDefaultServletWebSecurity}.
*
* @author Phillip Webb
*/
class DefaultWebSecurityCondition extends AllNestedConditions {
class DefaultServletWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
DefaultServletWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@@ -75,7 +75,7 @@ public final class ServletWebSecurityAutoConfiguration {
* part of the custom security configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnDefaultServletWebSecurity
static class SecurityFilterChainConfiguration {
@Bean
@@ -0,0 +1,63 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.security.autoconfigure.web.reactive;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.server.SecurityWebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConditionalOnDefaultReactiveWebSecurity}.
*
* @author Andy Wilkinson
*/
class ConditionalOnDefaultReactiveWebSecurityTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
void matchesWithoutSecurityWebFilterChainBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(TestConfiguration.class))
.run((context) -> assertThat(context).hasBean("testBean"));
}
@Test
void doesNotMatchWhenSecurityWebFilterChainBeanIsDefined() {
this.contextRunner.withBean(SecurityWebFilterChain.class, () -> mock(SecurityWebFilterChain.class))
.withConfiguration(AutoConfigurations.of(TestConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean("testBean"));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultReactiveWebSecurity
static class TestConfiguration {
@Bean
String testBean() {
return "test";
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.security.autoconfigure.web.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.SecurityFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConditionalOnDefaultServletWebSecurity}.
*
* @author Andy Wilkinson
*/
class ConditionalOnDefaultServletWebSecurityTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
void matchesWithoutSecurityFilterChainBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(TestConfiguration.class))
.run((context) -> assertThat(context).hasBean("testBean"));
}
@Test
void doesNotMatchWhenSecurityFilterChainBeanIsDefined() {
this.contextRunner.withBean(SecurityFilterChain.class, () -> mock(SecurityFilterChain.class))
.withConfiguration(AutoConfigurations.of(TestConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean("testBean"));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultServletWebSecurity
static class TestConfiguration {
@Bean
String testBean() {
return "test";
}
}
}