spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Read Body MVC Predicate Does Not Work With Spring Security On The Classpath

Open ryanjbaxter opened this issue 7 months ago • 0 comments

When using Spring Cloud Gateway MVC and the ready body predicate with Spring Security on the classpath results in

2025-05-29T11:12:38.272-04:00  WARN 93314 --- [o-auto-1-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No content to map due to end-of-input]
2025-05-29T11:12:38.272-04:00 TRACE 93314 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : No view rendering, null ModelAndView returned.
2025-05-29T11:12:38.273-04:00 DEBUG 93314 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 400 BAD_REQUEST, headers={X-Content-Type-Options:[nosniff], X-XSS-Protection:[0], Cache-Control:[no-cache, no-store, max-age=0, must-revalidate], Pragma:[no-cache], Expires:[0], X-Frame-Options:[DENY]}

when running ServerMvcIntegrationTests.readBodyWorks. This occurs even when disabling Spring Security with the following configuration:

@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).csrf(AbstractHttpConfigurer::disable);
		return http.build();
	}

After some debugging I think it has to do with the use of AttributesPreservingRequest(https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java#L540)

It seems to be used when the cache filter is created https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java#L266

The cache is created in WebSecurityConfiguration https://github.com/spring-projects/spring-security/blob/6.5.x/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java#L194

If in ServerMvcIntegrationTests.TestConfiguration I extend WebMvcConfigurationSupport and then override mvcHandlerMappingIntrospector doing the following

		@Bean
		@Lazy
		@Override
		public @NotNull HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
			return new HandlerMappingIntrospector() {
				@Override
				public @NotNull Filter createCacheFilter() {
					return (request, response, chain) -> {
						chain.doFilter(request, response);
					};
				}
			};
		}

Essentially disabling the caching filter which wraps the request AttributesPreservingRequest the readBodyWorks test will pass again.

The ReadBodyPredicate caches the body in attributes on the request since the body can only be ready once and then will use the cached body in the attribute when forwarding the request downstream. (See MVCUtils.cacheAndReadBody). Since the attributes don't actually get put on the request when the caching filter wraps the request in AttributesPreservingRequest when the Gateway goes to forward the request downstream it tries to read an empty value from the attribute and we get the JSON parse error.

ryanjbaxter avatar May 29 '25 15:05 ryanjbaxter