spring-security
spring-security copied to clipboard
DispatcherServletDelegatingRequestMatcher causes errors when running tests with MockMvc
Describe the bug
In a Spring Boot application with multiple servlets registered to the context (DispatcherServlet and at least one other), an IllegalArgumentException
with message Failed to find servlet [] in the servlet context
is thrown when running tests with MockMvc
and @SpringBootTest(webEnvironment = RANDOM_PORT)
.
The problem started occurring after an upgrade from Spring Boot 3.0.2 to Spring Boot 3.2.1 (Spring Security 6.2.1).
The exception is thrown in the DispatcherServletDelegatingRequestMatcher due to an incompatibility with the standard MockHttpServletRequest instances created with the MockMvcRequestBuilders class. These have a generic HttpRequestMapping with an empty String as the servletName, thus causing the Matcher to not find a ServletRegistration and throw the exception.
Gist with full stacktrace: Gist
To Reproduce Run the tests in the provided sample to reproduce.
In the sample is a simple Spring Boot app with an extra servlet registered to the context and a basic security setup. The included tests demonstrate 2 working cases and one failing case. The test TestWithRandomPortEnvironment#expected_failure demonstrates the exception. TestWithRandomPortEnvironment#expected_success demonstrates the fix I implemented to make this work with a RequestPostProcessor, but this solution would have to be applied everywhere I'm using MockMvc.
Expected behavior The DispatcherServletDelegatingRequestMatcher should be compatible with the standard MockHttpServletRequest instances created with the MockMvcHttpRequestBuilders and not throw an exception.
Sample Sample repository
I have a very similar problem that is not just limited to running tests with MockMvc. It can also happen in a Spring Boot application at runtime, if all of the following conditions are given:
-
management.server.port=8081
or anything that is not the same asserver.port
- Use
securityMatcher
in Spring Security DSL - Register a custom servlet
- Start the application and try to access a non-existing endpoint on the management port, e.g.
curl -i http://localhost:8081/foo
This will get you an internal server error with HTTP status code 500. With Spring Boot 3.1.0, you would see a 404 as expected for a non-existing endpoint.
The stacktrace is similar to the one from the initial post, but the message is slightly different. In my case, the servlet it fails to find does have a non-empty name: Failed to find servlet [dispatcherServletRegistration] in the servlet context
The sample repository from the initial post seems unavailable. I have created another sample repository with a sample application to reproduce the issue I described: https://github.com/atrepczik/spring-security-request-matching-issue
@LewisMcReu Could you perhaps add the fix you had in mind? I didn't manage to (yet) and the repository you mentioned is empty right now.
Nevermind,... I just figured it out I suppose:
static class FixMissingServletPathProcessor implements RequestPostProcessor {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setHttpServletMapping(new HttpServletMapping() {
@Override
public String getMatchValue() {
return "";
}
@Override
public String getPattern() {
return "";
}
@Override
public String getServletName() {
return "dispatcherServlet";
}
@Override
public MappingMatch getMappingMatch() {
return MappingMatch.PATH;
}
});
request.setContextPath("/");
return request;
}
}
Did anyone solve the issue. I am looking to solve this for a while. How do I get past this issue?
I have a similar issue and spent the last 3 days trying to reproduce it and find the exact version of spring-boot / spring-security where it occured for the first time.
Scenario:
- Spring Boot 3.1.x (just for testing, problem also occurs in 3.2.x)
- Management server on different port than main server (in my case
:8081
) - Configured
SecurityFilterChain
- Additional servlet configured (in my "real" project this is done by a third-party dependency, in the demo I did it manually)
Link to my example repo: https://github.com/seschi98/demo-spring-dispatcher-servlet-error
With Spring Boot 3.1.1 (Security 6.1.1) everything works as expected. No errors on startup, localhost:8081/actuator/health
displays {"status": "UP"}
. Nice!
With Spring Boot 3.1.2 (Security 6.1.2) the server won't start anymore. I could backtrack this problem to https://github.com/spring-projects/spring-security/commit/cf2c8da3d50647728ff1fb5c7c2e2e43cffe9232
With Spring Boot 3.1.3 (Security 6.1.3) its the same issue, just with a better error message. See https://github.com/spring-projects/spring-security/commit/0df188437263aeeab61f154c3ba823d45f990b9f
With Spring Boot 3.1.4 (Security 6.1.4), Spring Boot 3.1.5 (Security 6.1.5) and Spring Boot 3.1.6 (Security 6.1.5) nothing changed.
With Spring Boot 3.1.7 (Security 6.1.6) the server starts again (yayyy), probably thanks to https://github.com/spring-projects/spring-security/commit/624dcafcf2f6c57183e006c491ffa75d59b56bd6. Unfortunately when you call localhost:8081/actuator/health
now, it throws the HTTP 500 error that was mentioned before:
java.lang.IllegalArgumentException: Failed to find servlet [dispatcherServletRegistration] in the servlet context
at org.springframework.util.Assert.notNull(Assert.java:204) ~[spring-core-6.0.15.jar:6.0.15]
at org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry$DispatcherServletDelegatingRequestMatcher.matcher(AbstractRequestMatcherRegistry.java:544) ~[spring-security-config-6.1.6.jar:6.1.6]
If I remove the line
management.server.port: 8081
from my application.yaml
, the health endpoint works normally (of course now with port :8080
).
I hope this information helps understanding the issue and someone with better knowledge of the spring-security internals will be able to figure out a solution for this.
I have the same problem. On our side it started to appear after adding random port to spring boot test annotation :(
Not working: @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) Working: @SpringBootTest
Spring 3.2.2: Exception: java.lang.IllegalArgumentException: Failed to find servlet [] in the servlet context
Having the same problem, but only with some of our applications. Digging into the difference, the problem is with the DispatcherServletDelegatingRequestMatcher and only comes into play if there are more than one Servlet registrations, for us at least. I have not tied it to the Random port, as our app does in fact have two servlet registrations so I debugged just that configuration.
The key decision point is in AbstractRequestMatcherRegistry, here.
It does NOT use the MVC matcher, but picks the Delegating matcher instead. The delegating matcher does not seem to work with the MockMvc request as the servlet name is in fact null when it gets to here in DispatcherServletDelegatingRequestMatcher.
AbstractRequestMatcherRegistry does say it's new since 3.2, so seems like a regression when using MockMvc in tests.
I faced same issue in my service, the problem is when you are using:
config.requestMatchers("/hello").permitAll();
then it delegates creating AntPathRequestMatcher to AbstractRequestMatcherRegistry here.
After that the resolve method is called (this one) which checks if there is only one dispatcherServlet in context configured (here). And when you declare additional dispatcherServlet then ant and mvc mapper is wrapped within DispatcherServletDelegatingRequestMatcher which is looking for bean with the name: dispatcherServletRegistration which is initialized in LAZY MODE.
In other words it is initialized after first call for different port e.g. localhost:8081/actuator/health so It can not be find in the context which has been configured during app initialization and as a result we are facing this issue. The simples fix is to just declare your own AntPathRequestMatcher and this will skip the steps I described before e.g.
config.requestMatchers(AntPathRequestMatcher.antMatcher("/hello")).permitAll();
But still IMHO it's bug within DispatcherServletDelegatingRequestMatcher which has to be addressed and fixed
This is not only happening in tests: https://stackoverflow.com/questions/77710370/actuator-endpoints-returning-500-error-after-upgrade-to-spring-boot-3 https://stackoverflow.com/questions/77785192/spring-boot-3-missing-dispatcherservletregistration-bean-in-actuator
I sprinkled some "AntPathRequestMatcher" calls around in my code, but imho this needs more attention as its a regression (that strangely enough not more people run into)
So, no official response from spring-security
team yet about this bug 😞
Got all the same issues described above after 3.2.x migration.