From 7c23813752edc943243dff51382d11b95f335cda Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Thu, 28 May 2026 15:55:38 +0200 Subject: [PATCH] Restore Spring Security integration with HtmlUnitDriver Prior to this commit, securityDelegateMockMvcHtmlUnitDriverCustomizer did not have an @Bean annotation and therefore was not registered as a bean in SecurityMockMvcAutoConfiguration. See gh-50602 Signed-off-by: Dmytro Nosan --- .../SecurityMockMvcAutoConfiguration.java | 1 + ...SecurityMockMvcAutoConfigurationTests.java | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 module/spring-boot-security-test/src/test/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfigurationTests.java diff --git a/module/spring-boot-security-test/src/main/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfiguration.java b/module/spring-boot-security-test/src/main/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfiguration.java index 32c9b6b1387..e6766d4d05f 100644 --- a/module/spring-boot-security-test/src/main/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfiguration.java +++ b/module/spring-boot-security-test/src/main/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfiguration.java @@ -60,6 +60,7 @@ public final class SecurityMockMvcAutoConfiguration { @ConditionalOnClass(HtmlUnitDriver.class) static class SecurityMockMvcHtmlUnitDriverConfiguration { + @Bean MockMvcHtmlUnitDriverCustomizer securityDelegateMockMvcHtmlUnitDriverCustomizer() { return (driver) -> driver .setExecutor(new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor())); diff --git a/module/spring-boot-security-test/src/test/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfigurationTests.java b/module/spring-boot-security-test/src/test/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfigurationTests.java new file mode 100644 index 00000000000..9989d9d6c5e --- /dev/null +++ b/module/spring-boot-security-test/src/test/java/org/springframework/boot/security/test/autoconfigure/webmvc/SecurityMockMvcAutoConfigurationTests.java @@ -0,0 +1,111 @@ +/* + * 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.test.autoconfigure.webmvc; + +import java.util.concurrent.Executor; +import java.util.function.Function; + +import jakarta.servlet.Filter; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.security.test.autoconfigure.webmvc.SecurityMockMvcAutoConfiguration.SecurityMockMvcBuilderCustomizer; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webmvc.test.autoconfigure.MockMvcHtmlUnitDriverCustomizer; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SecurityMockMvcAutoConfiguration}. + * + * @author Dmytro Nosan + */ +class SecurityMockMvcAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SecurityMockMvcAutoConfiguration.class)); + + @Test + void securityMockMvcBuilderCustomizerIsNotRegisteredWhenMockMvcIsNotOnTheClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(MockMvc.class)) + .with(securityFilterChain()) + .run((context) -> assertThat(context).doesNotHaveBean(MockMvcHtmlUnitDriverCustomizer.class) + .doesNotHaveBean(SecurityMockMvcBuilderCustomizer.class)); + } + + @Test + void securityMockMvcBuilderCustomizerIsNotRegisteredWhenSecurityFilterChainIsMissing() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(SecurityMockMvcBuilderCustomizer.class)); + } + + @Test + void htmlUnitDriverCustomizerIsNotRegisteredWhenHtmlUnitIsNotOnTheClasspath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(HtmlUnitDriver.class)) + .run((context) -> assertThat(context).doesNotHaveBean(MockMvcHtmlUnitDriverCustomizer.class)); + } + + @Test + void registersSecurityMockMvcBuilderCustomizerWhenSecurityFilterChainIsPresent() { + this.contextRunner.with(securityFilterChain()) + .run((context) -> assertThat(context).hasSingleBean(SecurityMockMvcBuilderCustomizer.class) + .getBean(SecurityMockMvcBuilderCustomizer.class) + .satisfies((customizer) -> { + ConfigurableMockMvcBuilder builder = mock(ConfigurableMockMvcBuilder.class); + customizer.customize(builder); + ArgumentCaptor configurerCaptor = ArgumentCaptor + .forClass(MockMvcConfigurer.class); + then(builder).should().apply(configurerCaptor.capture()); + RequestPostProcessor postProcessor = configurerCaptor.getValue() + .beforeMockMvcCreated(builder, mock(WebApplicationContext.class)); + assertThat(postProcessor) + .hasSameClassAs(SecurityMockMvcRequestPostProcessors.testSecurityContext()); + })); + } + + @Test + void registersSecurityMockMvcHtmlUnitDriverCustomizerWhenHtmlUnitIsPresent() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MockMvcHtmlUnitDriverCustomizer.class) + .getBean(MockMvcHtmlUnitDriverCustomizer.class) + .satisfies((customizer) -> { + HtmlUnitDriver htmlUnitDriver = mock(HtmlUnitDriver.class); + customizer.customize(htmlUnitDriver); + ArgumentCaptor executorCaptor = ArgumentCaptor.forClass(Executor.class); + then(htmlUnitDriver).should().setExecutor(executorCaptor.capture()); + assertThat(executorCaptor.getValue()).isInstanceOf(DelegatingSecurityContextExecutor.class); + })); + } + + private Function securityFilterChain() { + return (contextRunner) -> contextRunner.withBean(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, + Filter.class, () -> mock(Filter.class)); + } + +}