spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

Allow Jackson StreamReadConstraints to be configured

Open pjfanning opened this issue 2 years ago • 35 comments

Jackson 2.15 (v2.15.0-rc1 is out) has a default set of limits that it applies to inputs. Input Files that breach those limits will cause parse exceptions.

spring-boot will need to consider if those limits are ok or if you need to provide a way to configure the Jackson StreamReadConstraints.

https://javadoc.io/static/com.fasterxml.jackson.core/jackson-core/2.15.0-rc1/com/fasterxml/jackson/core/StreamReadConstraints.html

pjfanning avatar Mar 21 '23 23:03 pjfanning

Thanks for raising this, @pjfanning.

At the ObjectMapper level, which is where Spring Boot operates, I can't quite see how to configure the constraints. As far as I can tell, they have to be configured on a JsonFactoryBuilder that's used to create a JsonFactory which is used in turn to create the ObjectMapper. That JsonFactory should be a MappingJsonFactory which, ideally, should be created with an ObjectMapper. I can't see how to create an ObjectMapper with a custom JsonFactory that itself has been created with that same ObjectMapper.

@cowtowncoder if you have a moment, could you please point us in the right direction here?

wilkinsona avatar Mar 22 '23 08:03 wilkinsona

Constructing a MappingJsonFactory from an existing ObjectMapper and then trying to override the StreamReadConstraints on the MappingJsonFactory - that is not currently allowed.

I'm not sure if this is something that jackson-databind should have but @cowtowncoder may see differently.

If there is no way for you to refactor your code, you could subclass jackson-databind's MappingJsonFactory and add a method to override the StreamReadConstraints.

pjfanning avatar Mar 22 '23 10:03 pjfanning

@wilkinsona could you highlight some code that you are worried about? I looked at spring-boot and all I can find is lots of code that uses new ObjectMapper().

Instead of new ObjectMapper(), you might find it a good idea to have a common ObjectMapperFactory, so that it easier to replace all the new ObjectMapper() calls with something like ObjectMapperFactory.createObjectMapper().

private static JsonFactory JSON_FACTORY;

// add some code to create JSON_FACTORY with your preferred StreamReadConstraints settings

public static createObjectMapper() {
   return JsonMapper.builder(JSON_FACTORY).build();
}

pjfanning avatar Mar 22 '23 14:03 pjfanning

My concern is a general one and doesn't really focus on any specific area of Spring Boot's code. If there's a mechanism in Jackson for configuring the constraints then based on our past experience with configuring Jackson I expect that we'll be able to use it without too much difficulty. It's a configuration mechanism that's similar to what Jackson offers in various other areas that I am looking for.

If there is one area that's likely to be of specific concern it is in and around org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration. This auto-configuration applies the user's spring.jackson.* application properties and makes extensive use of Spring Framework's Jackson2ObjectMapperBuilder to do so. If the constraints need to be configurable, some new spring.jackson.* properties would be the obvious way for users to do so. I don't yet see how we could apply those properties to either a new or an existing ObjectMapper instance.

wilkinsona avatar Mar 22 '23 14:03 wilkinsona

Jackson is moving towards making Object Mappers immutable. You may need to rethink the idea of reconfiguring at that level. Factories and Builders are where Jackson config happens or that's where things are moving.

pjfanning avatar Mar 22 '23 15:03 pjfanning

Ok, while @pjfanning is correct in that we are moving to Builder-based configuration, there does need to be some support for old-style direct configuration for compatibility reasons. For new functionality this may not be needed but for StreamReadConstraints we do need something, I think. Jackson 3.x (only) will offer/require full immutability; before that there is just availability of Builders with only partial benefits.

I will file a jackson-core issue to make sure there is actually a way to directly set configuration via JsonFactory -- I do believe that it is impractical to expect frameworks to switch to Builder-style construction (and more importantly, exposing that to their users) mid-Jackson-2.x

EDIT: created https://github.com/FasterXML/jackson-core/issues/962

Thank you @pjfanning for due diligency here: I think this is something that must be resolved for 2.15.0 final & is a good catch now (instead of after release).

cowtowncoder avatar Mar 22 '23 16:03 cowtowncoder

@wilkinsona I did add the obvious 2.x, non-builder mechanism (see https://github.com/FasterXML/jackson-core/issues/962), that is, JsonFactory.setStreamReadConstraints(). It will be in 2.15.0-rc2. Thank you for outlining this obvious (in hindsight, although I really should have seen it ahead of time too) gap.

As usual, help by Spring Boot team is much appreciated: as much as I want 2.15.0 to get done ASAP, I want to also avoid rushing and breaking existing usage by valued user communities.

cowtowncoder avatar Mar 23 '23 03:03 cowtowncoder

Thanks very much, @cowtowncoder.

wilkinsona avatar Mar 24 '23 14:03 wilkinsona

With the new setter, it'll be possible for someone to configure the constraints in a Spring Boot application without requiring any changes in Boot:

@Bean
Jackson2ObjectMapperBuilderCustomizer customStreamReadConstraints() {
	return (builder) -> builder.postConfigurer((objectMapper) -> objectMapper.getFactory()
		.setStreamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(2000).build()));
}

This will ensure that any ObjectMapper created through the auto-configured Jackson2ObjectMapperBuilder will have a max nesting depth of 2000. We can still consider adding some configuration properties for the three constraints if tuning them is a common requirement.

wilkinsona avatar Mar 24 '23 14:03 wilkinsona

Correct @wilkinsona, that's the idea.

cowtowncoder avatar Mar 24 '23 16:03 cowtowncoder

We've added a Jackson2ObjectMapperBuilderCustomizer to modify the max string length like @wilkinsona mentioned but we're not seeing this reflected in our tests that use an injected ObjectMapper. We've also tried using StreamReadConstraints.overrideDefaultStreamReadConstraints to force override at application startup, but again, no luck. Still hitting the 20,000,000 limit.

@Configuration
public class JacksonConfig {
    @Bean
    Jackson2ObjectMapperBuilderCustomizer customStreamReadConstraints() {
        return (builder) -> builder.postConfigurer((objectMapper) -> objectMapper.getFactory()
            .setStreamReadConstraints(StreamReadConstraints.builder().maxStringLength(100000000).build()));
    }
}
public static void main(String[] args) {
    StreamReadConstraints.overrideDefaultStreamReadConstraints(
        StreamReadConstraints.builder().maxStringLength(100000000).build()
    );
    SpringApplication.run(ApiApplication.class, args);
}

jamesdh avatar Jun 15 '23 16:06 jamesdh

The only way I could get this to work was to override the defaults in a static context, e.g.

@Configuration
public class JacksonConfig {
    static {
        StreamReadConstraints.overrideDefaultStreamReadConstraints(
            StreamReadConstraints.builder().maxStringLength(100000000).build()
        );
    }
}

jamesdh avatar Jun 15 '23 17:06 jamesdh

@jamesdh I can't reproduce the behaviour you've described in either case. If overrideDefaultStreamReadConstraints is not working, it sounds like something else is configuring the constraints and preventing the overridden defaults from taking effect. Please follow up with a question on Stack Overflow that includes a minimal example that reproduces the problem.

wilkinsona avatar Jun 15 '23 17:06 wilkinsona

@wilkinsona I got overrideDefaultStreamReadConstraints working but had to call it from a static context. I guess that implies some other bean/config in a 3rd party dependency could be at cause? We have nothing else in our code that touches the ObjectMapper config.

jamesdh avatar Jun 15 '23 17:06 jamesdh

It's really a Jackson question, but as far as I know changes to the defaults should affect every com.fasterxml.jackson.core.JsonFactory that's created thereafter. As I said above, please follow up on Stack Overflow as this issue isn't that right place for this.

wilkinsona avatar Jun 15 '23 18:06 wilkinsona

I think this issue should be closed as the original change was done.

And if there are follow-up issues, file issue in appropriate place(s).

cowtowncoder avatar Jun 15 '23 19:06 cowtowncoder

I'd like to keep the issue open to see if there's sufficient interest in some spring.jackson.* configuration properties for configuring the constraints. In the meantime, using a customizer or an early call to StreamReadConstraints.overrideDefaultStreamReadConstraints remains the recommended approach.

wilkinsona avatar Jun 15 '23 20:06 wilkinsona

We just ran into this situation. Received a constraints limit reached. It would be nice to be able to set this via properties as most of the configuration we do is via those properties.

jjstreet avatar Jul 20 '23 16:07 jjstreet

Jackson does not offer much any global configutation, by design -- it is typically embedded as a component so global configuration tends to break usage in unintended places.

So no support for property configuration will be added on Jackson side.

EDIT: please disregard, as per @wilkinsona comment I was confused about question; not related to Jackson side. Spring definitely has lots of relevant configurability.

cowtowncoder avatar Jul 26 '23 16:07 cowtowncoder

@cowtowncoder, I assume @jjstreet was referring to Spring Boot properties. We already provide several for configuring an ObjectMapper instance and could add to them to provide property-based configuration for its stream read constraints.

wilkinsona avatar Jul 26 '23 16:07 wilkinsona

@wilkinsona Thank you for pointing this out -- yeah I should pay attention to where issue exists, not just look at title & update :)

cowtowncoder avatar Jul 27 '23 15:07 cowtowncoder

@cowtowncoder, I assume @jjstreet was referring to Spring Boot properties. We already provide several for configuring an ObjectMapper instance and could add to them to provide property-based configuration for its stream read constraints.

It would be quite useful. I keep hitting certain limits every now and then. Copying the same configuration class to every microservice is quite suboptimal. Releasing a jar that has the ability to enable the configuration via properties and including it as a dependency in every app is even more problematic.

gyula-lakatos avatar Jul 31 '23 17:07 gyula-lakatos

I will say that we were able to route around the stream limit constraints by using the suggested customizer class.

Where would such a property exist? spring.jackson.deserialization.*?

jjstreet avatar Aug 04 '23 17:08 jjstreet

I'm not sure but it couldn't be a spring.jackson.deserialization property as they're a Map<DeserializationFeature, Boolean>. We'll have to give it some thought.

wilkinsona avatar Aug 04 '23 17:08 wilkinsona

The only way I could get this to work was to override the defaults in a static context, e.g.

@Configuration
public class JacksonConfig {
    static {
        StreamReadConstraints.overrideDefaultStreamReadConstraints(
            StreamReadConstraints.builder().maxStringLength(100000000).build()
        );
    }
}

This works to allow larger string/json as input

smarbl-AmitRohra avatar Feb 21 '24 16:02 smarbl-AmitRohra

Hi all, so for what I'm understanding, me that having many springboot 3.2.x so with jackson 2.15.4, there is no spring.jackson property currently available to increase that limit of 20MB? I've reached that limit with some email for that I need to post its content.

So the only solution is to modify the ObjectMapper? Thank you for the confirmation

rgambelli avatar Feb 26 '25 12:02 rgambelli

Hi @rgambelli

So the only solution is to modify the ObjectMapper? Thank you for the confirmation

Yes

there is no spring.jackson property currently available to increase that limit of 20MB?

There is no spring.jackson.* properties at the moment to configure StreamReadConstraints.


You can use the following configuration to modify StreamReadConstraints.


import com.fasterxml.jackson.core.StreamReadConstraints;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class JacksonConfiguration {

	@Bean
	Jackson2ObjectMapperBuilderCustomizer streamReadConstaintsCustomizer() {
		return (builder) -> builder.postConfigurer((objectMapper) -> objectMapper.getFactory()
				.setStreamReadConstraints(StreamReadConstraints.defaults()
						.rebuild()
						.maxDocumentLength()
						.maxStringLength()
						.maxNameLength()
						.maxNestingDepth()
						.maxTokenCount()
						.build()));
	}

}

}

nosan avatar Feb 26 '25 18:02 nosan

Sorry @nosan I'm trying with your class, I can confirm debugging that the flow goes through that builder but the issue is not fixed.

In this screenshot you can see the org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter class and as you can see the default objectmapper is selected nevertheless its stream read constraints are not changed according to the configuration.

I'm using springboot 3.2.4, thanks for your help

Image

rgambelli avatar Mar 26 '25 17:03 rgambelli

I don't understand the StreamReadConstraints that @nosan 's example creates. It appears to create a default constraints instance with the default limits. These default limits can be too low.

pjfanning avatar Mar 26 '25 18:03 pjfanning

@rgambelli

I've tested the StreamReadConstraints customizer with a small application, and everything works as expected.

The sample application can be downloaded using this link: gh-34709.zip

 :: Spring Boot ::                (v3.2.4)
2025-03-26T20:31:13.770+02:00  INFO 75386 --- [gh-34709] [    Test worker] task.gh34709.Gh34709ApplicationTest      : Starting Gh34709ApplicationTest using Java 17.0.13 with PID 75386 (started by dmytronosan in /Users/dmytronosan/IdeaProjects/gh-34709)
2025-03-26T20:31:13.770+02:00  INFO 75386 --- [gh-34709] [    Test worker] task.gh34709.Gh34709ApplicationTest      : No active profile set, falling back to 1 default profile: "default"
2025-03-26T20:31:14.086+02:00  INFO 75386 --- [gh-34709] [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 0 (http)
2025-03-26T20:31:14.091+02:00  INFO 75386 --- [gh-34709] [    Test worker] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-03-26T20:31:14.091+02:00  INFO 75386 --- [gh-34709] [    Test worker] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.19]
2025-03-26T20:31:14.116+02:00  INFO 75386 --- [gh-34709] [    Test worker] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-03-26T20:31:14.116+02:00  INFO 75386 --- [gh-34709] [    Test worker] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 339 ms
2025-03-26T20:31:14.241+02:00  INFO 75386 --- [gh-34709] [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 58741 (http) with context path ''
2025-03-26T20:31:14.245+02:00  INFO 75386 --- [gh-34709] [    Test worker] task.gh34709.Gh34709ApplicationTest      : Started Gh34709ApplicationTest in 0.565 seconds (process running for 0.955)
2025-03-26T20:31:14.650+02:00  INFO 75386 --- [gh-34709] [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-03-26T20:31:14.650+02:00  INFO 75386 --- [gh-34709] [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-03-26T20:31:14.651+02:00  INFO 75386 --- [gh-34709] [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms

2025-03-26T20:31:14.670+02:00  WARN 75386 --- [gh-34709] [o-auto-1-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Depth (65) exceeds the maximum allowed nesting depth (64)] 

nosan avatar Mar 26 '25 18:03 nosan