In my project, I have a set of api calls which should filtered through certain set of common validation. In that case, I have to intercept the request before it hits the REST controller, read the request body, do the validations and pass it to the controller if the request passes the validations.
Since the HttpServletRequest cannot be deserialized more than once, I used a HttpServletRequestWrapper to make a copy of the actual request. Using the copy it makes, I do the validations.
Following is the configuration class for intercepting the requests.
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
CustomInterceptor customInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor).addPathPatterns("/signup/**");
}
}
Here is my preHandle method inside CustomInterceptor class which extends HandlerInterceptorAdaptor
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
return false;
}
// some more validations which end up returning false if they are met
}
return true;
}
This is my HttpRequestWrapper
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public HttpRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
try {
requestBody = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
requestBody = new byte[0];
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
}
}
All set now. Now, when I send a request to any url with the pattern of /signup/**, all the validations are happening fine. However, once the request hits the controller method, error pops out saying the request body is not available.
Required request body is missing: public
com.mypackage.myResponseObject
com.mypackage.myController.myControllerMethod(com.mypackage.myDTO)
I am struggling to find the reason for this and also a way to overcome the issue. Is there anything I have done wrong in RequestWrapper class? or anything missing?
Help me to sort this thing out.
Thanks!
The Problem seems to be that you are using an Interceptor to read the HttpServletRequest's InputStream and just wrap it in HttpRequestWrapper but the wrapper is never returned.
I think you should use a Filter
public class CustomFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
//return false;
}
// some more validations which end up returning false if they are met
}
filterChain.doFilter(copiedRequest, (ServletResponse) response);
}
}
And you need to use this Filter in either web.xml or WebApplicationInitializer
Related
I have a requirement to inject custom headers into every request a spring boot application is getting, for this, I have written some code but it seems it is not doing its work. For a brief, I have implemented the Filter interface and defined the doFilter method, extended the HttpServletRequestWrapper class, and overridden getHeader() and getHeaderNames() method to take into account the custom headers I am reading from the properties file.
But, the moment I get into the controller and check the request I am not getting my custom headers that were set through the MyReqWrapper. Below is the code, I've also tried searching it in Stackoverflow but couldn't find the solution on what is/could be wrong here. Can someone point me in the right direction?
Also, please point me on how to test whether custom headers are actually set or not.
This is Filter implementation
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ReqFilter implements Filter {
private static final String CUSTOMHEADERENABLED = "customheadersenabled";
private static final String CUSTOMHEADERCOUNT = "customheaderscount";
#Autowired
private Environment env;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
boolean customHeadersEnabled = Boolean.parseBoolean(env.getProperty(CUSTOMHEADERENABLED, "false"));
int count = Integer.parseInt(env.getProperty(CUSTOMHEADERCOUNT, "0"));
if (customHeadersEnabled && count > 0) {
MyReqWrapper myReq = new MyReqWrapper((HttpServletRequest) servletRequest);
myReq.processMyHeaders(count, env);
filterChain.doFilter(customRequest, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
catch(ServletException ex){
throw ex;
}
}
#Override
public void destroy() {
//
}
}
This is custom request wrapper extending HttpServletRequestWrapper
final class MyReqWrapper extends HttpServletRequestWrapper {
private static final String CUSTOMHEADERPREFIX = "header1";
private final Map<String, String> myHeaders;
public MyReqWrapper(HttpServletRequest request) {
super(request);
myHeaders = new HashMap<>();
}
#Override
public String getHeader(String name) {
String headerValue = myHeaders.get(name);
if (headerValue != null){
return headerValue;
}
return ((HttpServletRequest) getRequest()).getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
Set<String> set = new HashSet<>(myHeaders.keySet());
Enumeration<String> headerNames = ((HttpServletRequest) getRequest()).getHeaderNames();
while (headerNames.hasMoreElements()) {
String n = headerNames.nextElement();
set.add(n);
}
return Collections.enumeration(set);
}
public void processMyHeaders(int headerCount, Environment env) {
while(headerCount > 0){
String [] headerKeyValue = Objects.requireNonNull(env.getProperty(String.format("%1$s%2$s", CUSTOMHEADERPREFIX, headerCount--)))
.split(":");
this.myHeaders.put(headerKeyValue[0], headerKeyValue[1]);
}
}
}
This was solved for me and I forgot to update this with an answer.
So the problem was I was using HttpServletRequest class from two different namespaces in the ReqFilter and controller classes, namely one from "org.apache.catalina.servlet4preview.http.HttpServletRequest" and another from "javax.servlet.http.HttpServletRequest".
Once I used uniform namespace in both the files I could access the headers from controller classes.
I had developed a Rest API application and have handled Authentication and Authorization using custom JWT.
I want to further make the application secure from XSS attacks or validation for untrusted data which could be handled for each and every field of JSON request.
Can I get some help in this regard so that efficient data processing will happen at the entry-level of the request without touching internal business validation?
You don't filter or escape data in a restful API. API's should be client agnostic. It is the clients responsibility to provide XSS protection. If the clients are doing their job appropriately you will end up with doubly escaped data. Remember potential clients can be:
Mobile Apps
Backend Web Servers
Web Browsers
Desktop Applications
Embedded systems/ IoT
In the above only a limited number of clients and configurations are vulnerable to XSS.
Need to override the HttpServletRequest in a Servlet Filter(if you are using Servlet).
Extends HttpServletRequestWrapper that stores JSON body(intention is to sanitize JSON body).
Strip/ escape the eligible JSON value
Extented "HttpServletRequestWrapper" :
public class SanitizationRequestWrapper extends HttpServletRequestWrapper {
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
private byte[] body;
public SanitizationRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
try {
body = IOUtils.toByteArray(super.getInputStream());
}catch (NullPointerException e){
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamImpl(new ByteArrayInputStream(body));
}
#Override
public BufferedReader getReader() throws IOException {
String enc = getCharacterEncoding();
if (enc == null) enc = "UTF-8";
return new BufferedReader(new InputStreamReader(getInputStream(), enc));
}
private class ServletInputStreamImpl extends ServletInputStream {
private InputStream is;
public ServletInputStreamImpl(InputStream is) {
this.is = is;
}
public int read() throws IOException {
return is.read();
}
public boolean markSupported() {
return false;
}
public synchronized void mark(int i) {
throw new RuntimeException(new IOException("mark/reset not supported"));
}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
}
}
Servlet filter which sanitize request body:
public class XSSSanitizeFilters implements Filter {
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
SanitizationRequestWrapper sanitizeRequest = new SanitizationRequestWrapper(request);
if (null != sanitizeRequest.getBody()) {
try {
sanitizeJson(sanitizeRequest);
} catch (ParseException e) {
LOG.error("Unable to Sanitize the provided JSON .");
}
arg2.doFilter(sanitizeRequest, arg1);
} else {
arg2.doFilter(arg0, arg1);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
private void sanitizeJson(SanitizationRequestWrapper sanitizeRequest ) throws IOException, ParseException {
JSONParser parser= new JSONParser();
Object obj = parser.parse(sanitizeRequest.getReader());
ObjectMapper oMapper = new ObjectMapper();
Map <String, Object> map = oMapper.convertValue(obj, Map.class);
sanitizeRequest.setBody((new JSONObject(map)).toString().getBytes());
}
For this you need XSS filter using HTMLUtils which will filter any injected script and prevent your site. Please refer my answer https://stackoverflow.com/a/55741351/10232467 for its complete code and implementation.
If your API doesn't accecpt any HTML Characters then you can follow the below logic.
You can Sanitize the Input Payload with EncodeHtml and Compare it with Provided Payload.
If both Sanitized Payload and Provided payload doesn't match then there exists some Html Content and straight way throw an Excpetion.
String unsanitizedPayload = IOUtils.toString(multiReadRequest.getReader());
String sanitizedPayload = Encode.forHtmlContent(unsanitizedPayload);
if(!unsanitizedPayload.equals(sanitizedPayload)) {
throw new Exception("Improper Payload");
}
If you're using Spring, Spring security guarantees basic level of protection against XSS attack. You can also use
#SafeHtml
private String value;
You will also need to add org.jsoup dependency.
I have a filter in my application
#Component
#Order(2)
public class RequestResponseLoggingFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// SET VALUE OF OBJECT
}
// other methods
}
I have a Restcall which uses a class.
#RequestMapping
Class Test{
#PostMapping("/test")
public void postEntry(#Valid #RequestBody Testing testing){
}
}
Class Testing{
#NotNull(message="ERROR")
String id;
....
}
I get the id in my filter and I would like to set the id of Testing class in my Filter. Is this possible?
You can use MockHttpServletRequest something like this
#Test
public void testAddEventWithWebAuthenticationDetails() {
HttpSession session = new MockHttpSession(null, "test-session-id");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(session);
request.setRemoteAddr("1.2.3.4");
WebAuthenticationDetails details = new WebAuthenticationDetails(request);
Map<String, Object> data = new HashMap<>();
data.put("test-key", details);
AuditEvent event = new AuditEvent("test-user", "test-type", data);
customAuditEventRepository.add(event);
List<PersistentAuditEvent> persistentAuditEvents = persistenceAuditEventRepository.findAll();
assertThat(persistentAuditEvents).hasSize(1);
PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0);
assertThat(persistentAuditEvent.getData().get("remoteAddress")).isEqualTo("1.2.3.4");
assertThat(persistentAuditEvent.getData().get("sessionId")).isEqualTo("test-session-id");
}
More examples here
or
if you want to do it filter way
few Points Before that
Request body can be read only once.
If you read the body in a filter, the target servlet will not be able to re-read it and this will also cause IllegalStateException.
You will need ServletRequestWrapper or its child: HttpServletRequestWrapper so that you can read HTTP request body and then the servlet can still read it later.
Workflow will be
The only way would be for you to consume the entire input stream yourself in the filter.
Take what you want from it, and then create a new InputStream for the content you read.
Put that InputStream in to a ServletRequestWrapper (or HttpServletRequestWrapper).
// Sample Wrapper class where you can read body and modify body content
public class SampleHttpServletRequest
extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public SampleHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
Filter class
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
doMyThing(multiReadRequest.getInputStream());
chain.doFilter(multiReadRequest, response);
}
}
Refer these post for more detail
Http Servlet request lose params from POST body after read it once
HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?
I have a REST api that responds with some additional non JSON data in the body content. This breaks the use of RestTemplate and jackson. Can I intercept the http response body prior to the parsing?
I am using RestTemplate.getForObject.
I've taken a look at the RestTemplate and couldn't see an appropriate method.
You can try to implement ClientHttpRequestInterceptor and assign it to restTemplate. Implement intercept method:
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
ClientHttpResponse response=clientHttpRequestExecution.execute(httpRequest, bytes);
//...do magic with response body from getBody method
return response;
}
You might have to extend AbstractClientHttpResponse with your own implementation to do that.
Another option could be to treat the response from the REST API as String, then format the string as needed and explicitly map it to object using ObjectMapper.
Then in your restTemplate you would have:
String result = restTemplate.getForObject(url, String.class, host);
//..trim the extra stuff
MyClass object=objectMapper.readValue(result, MyClass.class);
Yet another option would be to implement your own HttpMessageConverter which extends AbstractJackson2HttpMessageConverter and register it with restTemplate. In my opinion that would be the cleaneast from the Spring point of view
Another way would be to unwrap the response by implementing a ClientHttpRequestInterceptor along with a ClientHttpResponse.
#Component
public class MyInterceptor implements ClientHttpRequestInterceptor {
#Autowired
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory;
#Autowired
MyRequestAdvice requestAdvice;
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
byte[] wrappedBody = requestAdvice.wrapRequest(bytes);
ClientHttpResponse res = clientHttpRequestExecution.execute(httpRequest, wrappedBody);
return responseWrapperBeanFactory.apply(res);
}
}
Here's the bean config for the MyResponseWrapper:
#Bean
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory() {
return this::getMyResponseWrapper;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyResponseWrapper getMyResponseWrapper(ClientHttpResponse originalResponse) {
return new MyResponseWrapper(originalResponse);
}
#Bean
public RestTemplate restTemplate(#Autowired MyInterceptor interceptor) {
RestTemplate t = new RestTemplate();
t.setInterceptors(Arrays.asList(interceptor));
// other setup code
return t;
}
And here's the ClientHttpResponse implementation:
public class MyResponseWrapper implements ClientHttpResponse {
private byte[] filteredContent;
private ByteArrayInputStream responseInputStream;
private ClientHttpResponse originalResponse;
public MyResponseWrapper(ClientHttpResponse originalResponse) {
this.originalResponse = originalResponse;
try {
filteredContent = MyContentUnwrapper.unwrapResponse(originalResponse.getBody().readAllBytes());
} catch (Exception e) {
throw new RuntimeException("There was a problem reading/decoding the response coming from the service ", e);
}
}
#Override
public HttpStatus getStatusCode() throws IOException {
return originalResponse.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return originalResponse.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return originalResponse.getStatusText();
}
#Override
public void close() {
if (responseInputStream != null) {
try {
responseInputStream.close();
} catch (IOException e) { /* so long */}
}
}
#Override
public InputStream getBody() throws IOException {
if (responseInputStream == null) {
responseInputStream = new ByteArrayInputStream(filteredContent);
}
return responseInputStream;
}
#Override
public HttpHeaders getHeaders() {
return originalResponse.getHeaders();
}
}
From your Controller you can try to return a ResponseEntity and manipulate the entity object manually
If you don't need these extra properties you may add:
#JsonIgnoreProperties(ignoreUnknown = true)
to your mapping class.
From docs:
Property that defines whether it is ok to just ignore any unrecognized
properties during deserialization. If true, all properties that are
unrecognized -- that is, there are no setters or creators that accept them
-- are ignored without warnings (although handlers for unknown properties,
if any, will still be called) without exception.
Does not have any effect on serialization.
All of my controllers extend the following abstract class:
public abstract class AbstractController {
public HttpServletRequest request;
public HttpServletResponse response;
public ModelMap model;
}
Moreover, I implemented the following interceptor:
public class HttpRequestInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
if (handler instanceof AbstractController) {
AbstractController controller = (AbstractController) handler;
controller.request = request;
controller.response = response;
controller.model = new ModelMap();
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
if (handler instanceof AbstractController && modelAndView != null) {
AbstractController controller = (AbstractController) handler;
modelAndView.addAllObjects(controller.model);
}
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
This is a solution I found to improve the factorization of my code, since you won't need to pass the request, the response and the model as method parameters within your controllers. The solution works fine, until I found this issue:
public class HomeController extends AbstractController {
#RequestMapping
public void download1() {
// use the parent attribute response
File file = new File(MY_FILE_PATH);
InputStream in = new BufferedInputStream(new FileInputStream(file));
ServletOutputStream out = response.getOutputStream();
IOUtils.copy(in, out);
response.flushBuffer();
}
#RequestMapping
public void download2(HttpServletResponse response) {
// use the response passed as parameter
File file = new File(MY_FILE_PATH);
InputStream in = new BufferedInputStream(new FileInputStream(file));
ServletOutputStream out = response.getOutputStream();
IOUtils.copy(in, out);
response.flushBuffer();
}
}
Both of the two methods above make the browser downloading a file, but the download1 one generated an empty file while the download2 generates the original file as it should. Any idea why?
Thanks to the debugger, I noticed that in the postHandle method of the interceptor, the download2 method generates a modelAndView which equals null, while the download1 one generated an instanciated one. This should mean something for the issue, but I can't find what.
How get a response instanciated when passed as a parameter of a controller's method?
Don't do this :
public abstract class AbstractController {
public HttpServletRequest request;
public HttpServletResponse response;
public ModelMap model;
}
Instance variables in controllers (which have default scope of singleton btw) is a bad idea.
Just make something like this (to a txt file):
#RequestMapping(value="/download", method=RequestMethod.GET, produces=MediaType.APPLICATION_OCTET_STREAM_VALUE)
#ResponseBody
public String download(HttpServletResponse response) throws IOException {
response.setContentType("application/force-download");
FileReader fr = new FileReader("/folder/file.extension");
return IOUtils.toString(fr); // IOUtils come from Apache Commons IO
}