8

I am using Jersey in my Web-application. The data sent to the server is in JSON format, which in turn is unmarshalled at the server-end and the object obtained is used in further processing. The security audit raised some vulnerabilities for this approach.

My Rest Code:

@POST
@Path("/registerManga")
@Produces(MediaType.APPLICATION_JSON)
public Response registerManga(MangaBean mBean){
    System.out.println(mBean);
    return Response.status(200).build();
}

MangaBean:

public class MangaBean {
    public String title;
    public String author;

    @Override
    public String toString() {
        return "MangaBean [title=" + title + ", author=" + author + "]";
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }


}

The data is sent in this format:

["title":"Bleach","author":"kubo tite"]

The above data is successfully unmarshalled into an object and I get this as the output:

MangaBean [title=Bleach, author=kubo tite]

But if the data is changed to:

["title":"<script>alert("123");</script>","author":"kubo tite"]

A 500 internal server error occurs and is displayed to the user:

javax.servlet.ServletException: org.codehaus.jackson.JsonParseException: Unexpected character ('1' (code 49)): was expecting comma to separate OBJECT entries
 at [Source: org.apache.catalina.connector.CoyoteInputStream@19bd1ca; line: 1, column: 28]
    com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:420)
    com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537)
    com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:699)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

The unexpected occurrence of "" is causing errors in the parser. As the unmarshalling is done behind the scenes and I have no control over it, I am unable to handle the exception being raised.

My question is how can I handle this exception and return a proper response to the user instead of a stacktrace. Please advice.

Mono Jamoon
  • 4,437
  • 17
  • 42
  • 64

6 Answers6

17

Register an exception mapper to handle the JSON parsing exceptions:

@Provider
class JSONParseExceptionMapper implements ExceptionMapper< JsonParseException > {
    @Override
    public Response toResponse(final JsonParseException jpe) {
        // Create and return an appropriate response here
        return Response.status(Status.BAD_REQUEST)
                .entity("Invalid data supplied for request").build();
    }
}
Perception
  • 79,279
  • 19
  • 185
  • 195
  • I don't really understand how to use the @Provider syntax.. Can I use this to handle the following scenario: Unruly API returns [] when a Map is empty rather than {}. Seems like from the above, I could map this exception and handle it ? – deepwinter Jan 30 '14 at 03:10
  • 2
    I cannot for the life of me get this to work in Jersey 2.13. Ugh. – Bob Kuhar Jan 09 '15 at 05:47
  • I think the missing part here is that you still need to define to the jaxb `beforeUnmarshal(Unmarshaller u, Object parent)` callback event and register a listener that just rethrows the exception. – Mordechai Mar 01 '17 at 06:30
  • had to make the class public with wildfly 8.x, otherwise it complained about missing constructor – beat May 31 '18 at 15:54
  • @BobKuhar See also question [#34615242](https://stackoverflow.com/q/34615242) and try to `register` mapper and also apply `@Priority`. – lu_ko Feb 03 '20 at 14:09
2

If this exception mapper isn't being called (as John Ding's comment indicated), make sure you have it registered with your ResourceConfig.

Also, take note that when using the JacksonFeature (with Jersey 2.x) it includes an exception mapper for this, but if you register your own it will take precedence.

Here is an example of registering it by package. So put JSONParseExceptionMapper in "your.package.here" and it will get picked up.

@ApplicationPath("whatever")
public class MyAppResourceConfig extends ResourceConfig
{
    public MyAppResourceConfig()
    {
        packages("your.package.here");
        register(JacksonFeature.class);
    }
}

For the original question, you'll need to handle JsonMappingExceptionMapper as well.

max
  • 471
  • 6
  • 8
1

In addition to @Perception's answer, the missing part is that the exceptions get swallowed and not reported. You should pass a validation listener:

private static final String[] severity = new String[] {"Warning", "Error", "Fatal Error"};
void beforeUnmarshal(Unmarshaller u, Object parent) throws JAXBException {
    u.setEventHandler((evt) -> {
        throw new WebApplicationException(severity[evt.getSeverity()]+": Error while parsing request body: " + evt.getMessage(), evt.getLinkedException(), 400);
    });
}

Just drop this code in your JAXB bean and your all set.

Mordechai
  • 15,437
  • 2
  • 41
  • 82
0

Adding full class example of Perception answer

package com.shashi.service;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.apache.log4j.Logger;

import org.codehaus.jackson.JsonParseException;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.talklingo.service.v1.UserManagementService;
@Provider
public class JSONParseExceptionMapper implements ExceptionMapper< JsonParseException > {

 private static Logger logger = Logger
   .getLogger(JSONParseExceptionMapper.class);

   public Response toResponse(final JsonParseException jpe) {
         // Create and return an appropriate response here

         return Response.status(Status.BAD_REQUEST)
                 .entity("Invalid data supplied for request").build();

     }
}
Shashi
  • 12,487
  • 17
  • 65
  • 111
0

The other option could be in the ResourceConfig, register the JsonParseExceptionMapper class.

register(JsonParseExceptionMapper.class);

Dattatray
  • 1,745
  • 1
  • 21
  • 49
0

Add the exception handling class in same package(or sub package) mentioned in the web.xml file

user
  • 1
  • 3