BAEL-7200

Modify Request Body Before Reaching Controller in Spring Boot
This commit is contained in:
parthiv39731 2023-11-18 12:34:13 +05:30
parent 336ee922eb
commit f90651c5f9
13 changed files with 472 additions and 0 deletions

View File

@ -49,6 +49,10 @@
<artifactId>commons-configuration</artifactId>
<version>${commons-configuration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
@ -61,6 +65,14 @@
<layout>JAR</layout>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -0,0 +1,11 @@
package com.baeldung.modifyrequest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.baeldung.modifyrequest")
public class ModifyRequestApp {
public static void main(String[] args) {
SpringApplication.run(ModifyRequestApp.class, args);
}
}

View File

@ -0,0 +1,74 @@
package com.baeldung.modifyrequest.aop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
@RestControllerAdvice
@Profile("aspectExample")
public class EscapeHtmlAspect implements RequestBodyAdvice {
private static final Logger logger = LoggerFactory.getLogger(EscapeHtmlAspect.class);
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//Apply this to all Controllers
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
logger.info("beforeBodyRead called");
InputStream inputStream = inputMessage.getBody();
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(escapeHtml(inputStream).getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// Return the modified object after reading the body
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//return the original body
return body;
}
private String escapeHtml(InputStream inputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try (inputStream) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
String input = stringBuilder.toString();
// Escape HTML characters
return input.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
}

View File

@ -0,0 +1,25 @@
package com.baeldung.modifyrequest.config;
import com.baeldung.modifyrequest.interceptor.EscapeHtmlRequestInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@Profile("interceptorExample")
public class WebMvcConfiguration implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebMvcConfiguration.class);
@Override
public void addInterceptors(InterceptorRegistry registry) {
logger.info("addInterceptors() called");
registry.addInterceptor(new EscapeHtmlRequestInterceptor())
.addPathPatterns("/save");
WebMvcConfigurer.super.addInterceptors(registry);
}
}

View File

@ -0,0 +1,23 @@
package com.baeldung.modifyrequest.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class UserController {
Logger logger = LoggerFactory.getLogger(UserController.class);
@PostMapping(value = "save")
public ResponseEntity<String> saveUser(@RequestBody String user) {
logger.info("save user info into database");
ResponseEntity<String> responseEntity = new ResponseEntity<>(user, HttpStatus.CREATED);
return responseEntity;
}
}

View File

@ -0,0 +1,27 @@
package com.baeldung.modifyrequest.filter;
import com.baeldung.modifyrequest.requestwrapper.EscapeHtmlRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@Order(1)
@Profile("filterExample")
public class EscapeHtmlFilter implements Filter {
Logger logger = LoggerFactory.getLogger(EscapeHtmlFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
logger.info("Modify the request");
filterChain.doFilter(new EscapeHtmlRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.modifyrequest.interceptor;
import com.baeldung.modifyrequest.requestwrapper.EscapeHtmlRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class EscapeHtmlRequestInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(EscapeHtmlRequestInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
EscapeHtmlRequestWrapper escapeHtmlRequestWrapper = new EscapeHtmlRequestWrapper(request);
return true;
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.modifyrequest.requestwrapper;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class EscapeHtmlRequestWrapper extends HttpServletRequestWrapper {
private String body = null;
public EscapeHtmlRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = this.escapeHtml(request);
}
private String escapeHtml(HttpServletRequest request) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try (InputStream inputStream = request.getInputStream()) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
String input = stringBuilder.toString();
// Escape HTML characters
return input.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
//.replaceAll("\"", "&quot;")
.replaceAll("'", "&#39;");
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

View File

@ -0,0 +1,31 @@
@startuml
'https://plantuml.com/sequence-diagram
skinparam sequenceMessageAlign direction
skinparam handwritten true
skinparam sequence {
ParticipantBackgroundColor beige
ParticipantPadding 50
}
autonumber
Browser -[#63b175]> Filter: HTTP Request
activate Browser
activate Filter
Filter -[#63b175]> Filter: doFilter()
Filter -[#63b175]> DispatcherServlet: HTTP Request
activate DispatcherServlet
DispatcherServlet -[#63b175]> Controller: HTTP Request
activate Controller
Controller --[#63b175]> DispatcherServlet: HTTP Response
deactivate Controller
DispatcherServlet --[#63b175]> Filter: HTTP Response
deactivate DispatcherServlet
Filter --[#63b175]> Browser: HTTP Response
deactivate Filter
deactivate Browser
@enduml

View File

@ -0,0 +1,33 @@
@startuml
'https://plantuml.com/sequence-diagram
skinparam sequenceMessageAlign direction
skinparam handwritten true
skinparam sequence {
ParticipantBackgroundColor beige
ParticipantPadding 50
}
autonumber
Browser -[#63b175]> Filter: Http Request
activate Browser
activate Filter
Filter -[#63b175]> DispatcherServlet: Http Request
activate DispatcherServlet
DispatcherServlet -[#63b175]> Interceptor: Http Request
activate Interceptor
Interceptor -[#63b175]> Interceptor: preHandle()
Interceptor -[#63b175]> Controller: Http Request
activate Controller
Controller --[#63b175]> Interceptor: Http Response
deactivate Controller
Interceptor --[#63b175]> DispatcherServlet: Http Response
deactivate Interceptor
DispatcherServlet --[#63b175]> Filter: Http Response
deactivate DispatcherServlet
Filter --[#63b175]> Browser: Http Response
deactivate Filter
deactivate Browser
@enduml

View File

@ -0,0 +1,52 @@
package com.baeldung.modifyrequest;
import com.baeldung.modifyrequest.controller.UserController;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.net.URI;
import java.util.Map;
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@WebMvcTest(UserController.class)
@ActiveProfiles("aspectExample")
public class EscapeHtmlAspectIntegrationTest {
Logger logger = LoggerFactory.getLogger(EscapeHtmlAspectIntegrationTest.class);
@Autowired
private MockMvc mockMvc;
@Test
void givenAspect_whenEscapeHtmlAspect_thenEscapeHtml() throws Exception {
Map<String, String> requestBody = Map.of(
"name", "James Cameron",
"email", "<script>alert()</script>james@gmail.com"
);
Map<String, String> expectedResponseBody = Map.of(
"name", "James Cameron",
"email", "&lt;script&gt;alert()&lt;/script&gt;james@gmail.com"
);
ObjectMapper objectMapper = new ObjectMapper();
mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save"))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(expectedResponseBody)));
}
}

View File

@ -0,0 +1,51 @@
package com.baeldung.modifyrequest;
import com.baeldung.modifyrequest.controller.UserController;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.net.URI;
import java.util.Map;
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@WebMvcTest(UserController.class)
@ActiveProfiles("filterExample")
public class EscapeHtmlFilterIntegrationTest {
Logger logger = LoggerFactory.getLogger(EscapeHtmlFilterIntegrationTest.class);
@Autowired
private MockMvc mockMvc;
@Test
void givenFilter_whenEscapeHtmlFilter_thenEscapeHtml() throws Exception {
Map<String, String> requestBody = Map.of(
"name", "James Cameron",
"email", "<script>alert()</script>james@gmail.com"
);
Map<String, String> expectedResponseBody = Map.of(
"name", "James Cameron",
"email", "&lt;script&gt;alert()&lt;/script&gt;james@gmail.com"
);
ObjectMapper objectMapper = new ObjectMapper();
mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save"))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(expectedResponseBody)));
}
}

View File

@ -0,0 +1,46 @@
package com.baeldung.modifyrequest;
import com.baeldung.modifyrequest.controller.UserController;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.net.URI;
import java.util.Map;
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@WebMvcTest(UserController.class)
@ActiveProfiles("interceptorExample")
public class EscapeHtmlInterceptorIntegrationTest {
Logger logger = LoggerFactory.getLogger(EscapeHtmlInterceptorIntegrationTest.class);
@Autowired
private MockMvc mockMvc;
@Test
void givenInterceptor_whenEscapeHtmlInterceptor_thenEscapeHtml() throws Exception {
Map<String, String> requestBody = Map.of(
"name", "James Cameron",
"email", "<script>alert()</script>james@gmail.com"
);
ObjectMapper objectMapper = new ObjectMapper();
mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save"))
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody)))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
}