Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom HTTP status response with JAX-RS (Jersey) and @RolesAllowed

With my very simple JAX-RS service I'm using Tomcat with JDBC realm for authentication, therefore I'm working the the JSR 250 annotations.

The thing is that I want to return a custom message body in the HTTP status response. The status code (403) should stay the same. For example, my service looks like the following:

@RolesAllowed({ "ADMIN" })
@Path("/users")
public class UsersService {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public String getUsers() {
        // get users ...
        return ...;
    }
}

If a user with a different role than "ADMIN" access the service, I want to change the response message to something like that (depending on the media type [xml/json]):

<error id="100">
    <message>Not allowed.</message>
</error>

At the moment Jersey returns the following body:

HTTP Status 403 - Forbidden

type Status report
message Forbidden
description Access to the specified resource (Forbidden) has been forbidden.
Apache Tomcat/7.0.12

How can I change the default message body? Is there a way to handle the (maybe thrown) exception to build my own HTTP status response?

like image 532
Stefan Avatar asked Jun 29 '11 11:06

Stefan


2 Answers

The easiest way to handle this sort of thing is to throw an exception and to register an exception mapper to convert into the kind of message you want to send in that case. So, suppose you throw an AccessDeniedException, you would then have a handler like this (with full class names in places for clarity):

@javax.ws.rs.ext.Provider
public class AccessDeniedHandler
        implements javax.ws.rs.ext.ExceptionMapper<AccessDeniedException> {
    public javax.ws.rs.core.Response toResponse(AccessDeniedException exn) {
        // Construct+return the response here...
        return Response.status(403).type("text/plain")
                .entity("get lost, loser!").build();
    }
}

The way in which you register the exception mapper varies according to the framework you're using, but for Jersey you should be fine with just using @Provider. I'll let you figure out for yourself how you want to generate the kind of error documents that you want, but I do recommend handling failures as HTTP error codes of some kind (that's more RESTful...)

like image 178
Donal Fellows Avatar answered Oct 05 '22 21:10

Donal Fellows


With creating an ExceptionMapper (mapping exceptions of WebApplicationException) it is possible to "catch" certain exceptions thrown by the application:

@Provider
public class MyExceptionMapper implements ExceptionMapper<WebApplicationException> {

    @Override
    public Response toResponse(WebApplicationException weException) {

        // get initial response
        Response response = weException.getResponse();

        // create custom error
        MyError error = ...;

        // return the custom error
        return Response.status(response.getStatus()).entity(error).build();
    }
}

You also need to add the package to your application web.xml for registering the provider:

<init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>
        com.myapp.userservice; // semi-colon seperated
        com.myapp.mappedexception
    </param-value>
</init-param>
like image 40
Stefan Avatar answered Oct 05 '22 21:10

Stefan