10

I have a RESTful endpoint which I secured with a simple authorization check via a custom ContainerRequestFilter. The filter checks if all the information contained in the HTTP session is correct and if not it executes this:

requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.entity("Forbidden").build());

This is all fine and dandy. The strange thing is that when I make the same GET request again the Jersey server reports an NPE and does not return anything.

The NPE stacktrace:

Jul 20, 2016 5:27:53 PM org.glassfish.jersey.server.ServerRuntime$Responder writeResponse
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
java.lang.IllegalStateException: The output stream has already been closed.
    at org.glassfish.jersey.message.internal.CommittingOutputStream.setStreamProvider(CommittingOutputStream.java:147)
    at org.glassfish.jersey.message.internal.OutboundMessageContext.setStreamProvider(OutboundMessageContext.java:803)
    at org.glassfish.jersey.server.ContainerResponse.setStreamProvider(ContainerResponse.java:372)
    at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:694)
    at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:329)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:384)
    at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:224)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
    at java.lang.Thread.run(Thread.java:745)

What is happening? I dont want to close the output stream. I only need a method to return a http return code + message to the requester.

Alex
  • 459
  • 4
  • 16

4 Answers4

25

In case anyone would ever face the same issue I had here's the answer: you cannot re-use Responses! The output stream writer's instance is bound to the Request and as soon as you use it in abortWith() the stream will be committed forever. As such it can no longer be used to send responses.

You need to initialize a new Request from within the filter() method.

in simple terms, do not use such constructs:

 private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
            .entity("Access blocked for all users !!").build();

btw, I've picken up this broken piece of code here: http://howtodoinjava.com/jersey/jersey-rest-security/ So be wary when implementing this RequestFilter.

Alex
  • 459
  • 4
  • 16
8

As mentioned, the problem is on the static ACCESS_DENIED Response that were already set.

private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build();

So replace:

requestContext.abortWith(ACCESS_DENIED)

by

requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build());
Ricardo Gellman
  • 623
  • 7
  • 17
1

You need not use requestContext.abortWith() inside the filter method. Instead, do the following.

    String authCredentials = containerRequest.getHeaderString(HttpHeaders.AUTHORIZATION);

The header sent from your REST client [browser/postman/SoapUI/jersey client program] will be of the format "Basic encodedString". The encodedString can be easily decoded using java.util.Base64. After decoding it at server end, the string will be of format username:password. Use String.split(":");to separate user name and password. Proceed to validation. If validation turns false, throw WebApplicationException from the ContainerRequestFilter's filter method that you are overriding here.

Kavitha Karunakaran
  • 1,340
  • 1
  • 17
  • 32
0

I totally agree. I found this sample from RESTful Java with JAX-RS 2.0, 2nd Edition that I found helpful after an afternoon of digging.

import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.NotAuthorizedException;

@Provider
@PreMatching

public class BearerTokenFilter implements ContainerRequestFilter {
   public void filter(ContainerRequestContext ctx) throws IOException {
     String authHeader = request.getHeaderString(HttpHeaders.AUTHORIZATION);
     if (authHeader == null) throw new NotAuthorizedException("Bearer");
     String token = parseToken(authHeader);
     if (verifyToken(token) == false) {

       throw new NotAuthorizedException("Bearer error=\"invalid_token\"");
    }
 }

private String parseToken(String header) {...}
private boolean verifyToken(String token) {...}
}

"In this example, if there is no Authorization header or it is invalid, the request is aborted with a NotAuthorizedException. The client receives a 401 response with a WWW-Authenticate header set to the value passed into the constructor of NotAuthorizedException. If you want to avoid exception mapping, then you can use the ContainerRequestContext.abortWith() method instead. Generally, however, I prefer to throw exceptions."

I think, this is why the author preferred throwing exceptions.

Community
  • 1
  • 1
theeGwandaru
  • 400
  • 2
  • 14