1

In my Spring Boot application (2.5.5) I get a large JSON body in the POST request to a specific endpoint. On that request I need to get both the parsed object and that whole object as a string to do some validation. The JSON object contains a lot of information that I don't need so that is not included in the Object so I can't convert it to a string.

@RestController @RequestMapping("/example") public class ExampleController { @PostMapping("") public void example( @RequestBody String stringBody, @RequestBody ExampleRequest exampleRequest ) { // Validate request with 'stringBody' // Do things with 'exampleRequest' } } 

The best idea I had so far is to just use @RequestBody String stringBody and then convert that string to a JSON object but that is really not the ideal solution.

I know that you can't have two @RequestBody but I really need to somehow have both.

3
  • Why do you need the whole object as a String to do some validation? What kind of validation? Commented Sep 26, 2021 at 22:12
  • @JoãoDias I need to verify the Ed25519 signature Commented Sep 26, 2021 at 23:07
  • To be more exact I need to implement this in Spring Boot canary.discord.com/developers/docs/interactions/… Commented Sep 26, 2021 at 23:09

1 Answer 1

2

I believe that a custom HandlerMethodArgumentResolver is your best option. For that I suggest you create a custom annotation as follows:

@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ValidJsonSignature { } 

Now you need to implement the custom HandlerMethodArgumentResolver:

public class JsonSignatureValidationArgumentResolver implements HandlerMethodArgumentResolver { private final ObjectMapper objectMapper; public JsonSignatureValidationArgumentResolver(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterAnnotation(ValidJsonSignature.class) != null; } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { HttpServletRequest httpServletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class); String jsonPayload = StreamUtils.copyToString(httpServletRequest.getInputStream(), StandardCharsets.UTF_8); // Do actual validation here if (// Valid) { // If valid, then convert String to method parameter type and return it return objectMapper.treeToValue(objectMapper.readTree(jsonPayload), methodParameter.getParameterType()); } else { // Throw exception if validation failed } } } 

Next, you need to register JsonSignatureValidationArgumentResolver as an argument resolver:

@Configuration public class JsonSignatureValidationConfiguraion implements WebMvcConfigurer { @Autowired private ObjectMapper objectMapper; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new JsonSignatureValidationArgumentResolver(objectMapper)); } } 

Last but not the least, you need to annotate the Controller attribute with @ValidJsonSignature as follows:

@RestController @RequestMapping("/example") public class ExampleController { @PostMapping("") public void example(@ValidJsonSignature ExampleRequest exampleRequest) { } } 
Sign up to request clarification or add additional context in comments.

4 Comments

Oh that's perfect! And there is no issues with using httpServletRequest.getInputStream()? I messed around with HandlerInterceptor and the app always "crashed" on the actual handler
To be sure nothing better than actually test it with your exact use case ;). In theory, this works but we can't be sure until we see it running.
Works like a charm! The only issue with the code you posted is @RequestBody @ValidJsonSignature ExampleRequest exampleRequest. @RequestBody overrides the @ValidJsonSignature so it never gets executed. If we leave it with just @ValidJsonSignature ExampleRequest exampleRequest it works as it should.
Awesome. I've updated my answer so that others may benefit from it. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.