From c35d758bff979f5faeadc869a69e82ad532a66eb Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Tue, 18 Nov 2025 23:08:58 +0700 Subject: [PATCH 1/4] Prevent UnsupportedOperationException Signed-off-by: Tran Ngoc Nhan --- .../springframework/security/web/FilterInvocation.java | 9 ++++++++- .../security/web/FilterInvocationTests.java | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/FilterInvocation.java b/web/src/main/java/org/springframework/security/web/FilterInvocation.java index 26874c31be5..8758ab327b9 100644 --- a/web/src/main/java/org/springframework/security/web/FilterInvocation.java +++ b/web/src/main/java/org/springframework/security/web/FilterInvocation.java @@ -178,6 +178,8 @@ static class DummyRequest extends HttpServletRequestWrapper { private final Map parameters = new LinkedHashMap<>(); + private final Map attributes = new LinkedHashMap<>(); + DummyRequest() { super(UNSUPPORTED_REQUEST); } @@ -189,7 +191,7 @@ public String getCharacterEncoding() { @Override public Object getAttribute(String attributeName) { - return null; + return this.attributes.get(attributeName); } void setRequestURI(String requestURI) { @@ -317,6 +319,11 @@ void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } + @Override + public void setAttribute(String name, Object value) { + this.attributes.put(name, value); + } + } static final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler { diff --git a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java index 98dafa27fc3..08ecb5cf27c 100644 --- a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java +++ b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java @@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; /** @@ -163,4 +164,11 @@ public void testDummyRequestGetHeadersNull() { assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(headers::nextElement); } + @Test + public void testDummyRequestGetAttribute() { + DummyRequest request = new DummyRequest(); + assertThatNoException().isThrownBy(() -> request.setAttribute("name", "value")); + assertThat(request.getAttribute("name")).isEqualTo("value"); + } + } From 9e0aca71541458583eb6b063acf7cacaf0d6fc02 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Tue, 18 Nov 2025 23:09:11 +0700 Subject: [PATCH 2/4] Prevent NPE Signed-off-by: Tran Ngoc Nhan --- .../web/AbstractRequestMatcherRegistry.java | 12 ++++++++-- .../AbstractRequestMatcherRegistryTests.java | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index 504f3b98408..29d3e1b848f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -427,12 +427,20 @@ RequestMatcher requestMatcher(ServletContext servletContext) { @Override public boolean matches(HttpServletRequest request) { - return this.requestMatcherFactory.apply(request.getServletContext()).matches(request); + ServletContext servletContext = request.getServletContext(); + if (servletContext == null) { + return false; + } + return this.requestMatcherFactory.apply(servletContext).matches(request); } @Override public MatchResult matcher(HttpServletRequest request) { - return this.requestMatcherFactory.apply(request.getServletContext()).matcher(request); + ServletContext servletContext = request.getServletContext(); + if (servletContext == null) { + return MatchResult.notMatch(); + } + return this.requestMatcherFactory.apply(servletContext).matcher(request); } @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index 3d5b25c7661..e829f4c453a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -42,6 +42,7 @@ import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher; import org.springframework.security.web.util.matcher.RegexRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -358,6 +359,19 @@ public void matchesWhenNoMappingThenException() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> requestMatcher.matcher(request)); } + @Test + public void requestMatchersWhenServletContextCanBeNullThenDisallow() { + TestDeferredRequestMatcherRegistry deferredRequestMatcherRegistry = new TestDeferredRequestMatcherRegistry(); + deferredRequestMatcherRegistry.setApplicationContext(this.context); + List requestMatchers = deferredRequestMatcherRegistry.requestMatchers("/**"); + MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/endpoint"); + + ReflectionTestUtils.setField(request, "servletContext", null); + + assertThat(requestMatchers).isNotEmpty().hasSize(1); + assertThat(requestMatchers.get(0).matches(request)).isFalse(); + } + private void mockMvcIntrospector(boolean isPresent) { ApplicationContext context = this.matcherRegistry.getApplicationContext(); given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent); @@ -392,6 +406,16 @@ private List unwrap(List wrappedMatchers) { } + private static class TestDeferredRequestMatcherRegistry + extends AbstractRequestMatcherRegistry> { + + @Override + protected List chainRequestMatchers(List requestMatchers) { + return requestMatchers; + } + + } + @Configuration @EnableWebSecurity @EnableWebMvc From fdd80a9da64ad7f07235ea6c920b621d6d8656f6 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Fri, 21 Nov 2025 18:42:02 +0700 Subject: [PATCH 3/4] Override remove method Signed-off-by: Tran Ngoc Nhan --- .../security/web/FilterInvocation.java | 13 +++++++++++++ .../security/web/FilterInvocationTests.java | 9 ++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/FilterInvocation.java b/web/src/main/java/org/springframework/security/web/FilterInvocation.java index 8758ab327b9..cb99c74d865 100644 --- a/web/src/main/java/org/springframework/security/web/FilterInvocation.java +++ b/web/src/main/java/org/springframework/security/web/FilterInvocation.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -321,9 +322,21 @@ void setServletContext(ServletContext servletContext) { @Override public void setAttribute(String name, Object value) { + Assert.notNull(name, "name can not be null"); this.attributes.put(name, value); } + @Override + public void removeAttribute(String name) { + Assert.notNull(name, "name can not be null"); + this.attributes.remove(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(new LinkedHashSet<>(this.attributes.keySet())); + } + } static final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler { diff --git a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java index 08ecb5cf27c..421b2070508 100644 --- a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java +++ b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java @@ -33,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; /** @@ -167,8 +166,12 @@ public void testDummyRequestGetHeadersNull() { @Test public void testDummyRequestGetAttribute() { DummyRequest request = new DummyRequest(); - assertThatNoException().isThrownBy(() -> request.setAttribute("name", "value")); - assertThat(request.getAttribute("name")).isEqualTo("value"); + request.setAttribute("name", "value"); + request.setAttribute("removeName", "removeValue"); + request.removeAttribute("remove"); + Enumeration attributeNames = request.getAttributeNames(); + assertThat(attributeNames.nextElement()).isEqualTo("name"); + assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(attributeNames::nextElement); } } From 07277a0b7b118a23779520246834a87be2aaeba1 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Fri, 21 Nov 2025 19:14:29 +0700 Subject: [PATCH 4/4] Update testcase Signed-off-by: Tran Ngoc Nhan --- .../org/springframework/security/web/FilterInvocationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java index 421b2070508..6f5715c850b 100644 --- a/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java +++ b/web/src/test/java/org/springframework/security/web/FilterInvocationTests.java @@ -168,7 +168,7 @@ public void testDummyRequestGetAttribute() { DummyRequest request = new DummyRequest(); request.setAttribute("name", "value"); request.setAttribute("removeName", "removeValue"); - request.removeAttribute("remove"); + request.removeAttribute("removeName"); Enumeration attributeNames = request.getAttributeNames(); assertThat(attributeNames.nextElement()).isEqualTo("name"); assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(attributeNames::nextElement);