Jersey 在使用 ContainerRequestContext.abortWith() 后为每次调用抛出 NPE

Jersey throws NPE for each call after using ContainerRequestContext.abortWith()

我有一个 RESTful 端点,我通过自定义 ContainerRequestFilter 通过简单的授权检查来保护它。过滤器检查 HTTP 会话中包含的所有信息是否正确,如果不正确,则执行此操作:

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

一切都很好,很漂亮。奇怪的是,当我再次发出相同的 GET 请求时,Jersey 服务器报告 NPE 并且没有 return 任何东西。

NPE 堆栈跟踪:

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.run(ServerRuntime.java:329)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors.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.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)

这是怎么回事?我不想关闭输出流。我只需要一个方法来 return 一个 http return 代码 + 消息给请求者。

万一有人遇到我遇到的同样问题,这里是答案: 您不能重复使用回复!输出流编写器的实例绑定到 Request,一旦您在 abortWith() 中使用它,流将永远提交。因此,它不能再用于发送响应。

您需要从 filter() 方法中初始化一个新请求。

简单来说,不要使用这样的结构:

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

顺便说一句,我在这里找到了这段损坏的代码: http://howtodoinjava.com/jersey/jersey-rest-security/ 所以在实现这个 RequestFilter 时要小心。

我完全同意。我从 RESTful Java with JAX-RS 2.0, 2nd Edition 中找到了这个示例,经过一个下午的挖掘后我发现它很有用。

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."

我想,这就是作者更喜欢抛出异常的原因。

如前所述,问题出在已经设置的静态 ACCESS_DENIED 响应上。

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

所以替换:

requestContext.abortWith(ACCESS_DENIED)

来自

requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Accesso não Autorizado").build());

您不需要在过滤方法中使用 requestContext.abortWith()。相反,请执行以下操作。

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

从您的 REST 客户端 [browser/postman/SoapUI/jersey 客户端程序] 发送的 header 将采用 "Basic encodedString" 格式。可以使用 java.util.Base64 轻松解码 encodedString。服务器端解码后,字符串格式为username:password。使用String.split(":");分隔用户名和密码。继续验证。 如果验证结果为假,则从您在此处覆盖的 ContainerRequestFilter 的过滤器方法中抛出 WebApplicationException