Java Spring-在处理后修改每个请求的头(在postHandle中)

Java Spring-在处理后修改每个请求的头(在postHandle中),java,spring,spring-mvc,servlets,Java,Spring,Spring Mvc,Servlets,我想做的是,在处理请求后向响应添加一个新的头。我需要检查已处理的HttpStatus代码(在我的例子中是401 unauthorized),并添加一个新的头。我知道Spring有拦截器,但响应不能按照以下说明进行修改: 请注意,HandlerInterceptor的postHandle方法并不总是非常适合与@ResponseBody和ResponseEntity方法一起使用。在这种情况下,HttpMessageConverter在调用postHandle之前写入并提交响应,这使得无法更改响应,例

我想做的是,在处理请求后向响应添加一个新的头。我需要检查已处理的
HttpStatus
代码(在我的例子中是401 unauthorized),并添加一个新的头。我知道Spring有拦截器,但响应不能按照以下说明进行修改:

请注意,HandlerInterceptor的postHandle方法并不总是非常适合与@ResponseBody和ResponseEntity方法一起使用。在这种情况下,HttpMessageConverter在调用postHandle之前写入并提交响应,这使得无法更改响应,例如添加标头。相反,应用程序可以实现ResponseBodyAdvice并将其声明为@ControllerAdvice bean,或者直接在RequestMappingHandlerAdapter上配置它

嗯,我实现了
ResponseBodyAdvice
。是的,它允许修改body,但我无法修改标题,事件无法找到从控制器返回的状态代码


另一个选项,使用servlet过滤器也不成功。我需要在filterChain.doFilter(servletRequest,servletResponse)之后添加头呼叫。但它同样不会修改标题值。有没有办法完成这个简单的任务?

您可以实现一个ServletFilter,只需包装原始响应对象

这将允许您推迟响应的实际写入,并添加自定义标题


另一方面:这看起来有点像Spring安全处理链。

听起来使用servlet过滤器是正确的,您可能需要做的是将servlet响应对象包装成一个检测401状态代码何时被设置并在此时添加自定义头的对象:

HttpServletResponse wrappedResponse = new HttpServletResponseWrapper(response) {

  public void setStatus(int code) {
    super.setStatus(code);
    if(code == 401) handle401();
  }

  // three similar methods for the other setStatus and the two
  // versions of sendError

  private void handle401() {
    this.addHeader(...);
  }
};

filterChain.doFilter(request, wrappedResponse);

Java将HTTP响应显示为一个对象,您可以独立地更改不同字段

但是服务器和客户机之间实际交换的是字节流,以及在正文之前发送的头和。这就是为什么HttpResponse有
isCommitted()
方法的原因:当发送了头时,响应被提交。当然,一旦提交,就不能再添加修改头了。一旦有足够的字符写入主体,servlet容器就可以提交并刷新响应

因此,在处理请求后尝试更改标头是不安全的。只有在请求尚未提交的情况下,它才能工作。唯一安全的情况是控制器本身不写响应,只是转发到视图。然后在
postHandle
interceptor方法中,响应尚未提交,您可以更改头。否则,必须测试
isCommitted()
,如果返回true。。。那么现在更改标题就太晚了


当然,在这种情况下,拦截器和过滤器都不能做任何事情…

如果不需要检查状态代码,那么您可以在预处理方法上添加这些头(因为Spring在postHandle激发之前提交响应,所以在postHandle中添加它们对于@ResponseBy标记的控制器方法返回的响应不起作用):

嗯,我执行了建议。是的,它允许你的身体 修改,但我无法修改标题,事件 找不到从控制器返回的状态代码

实际上,如果您将
ServerHttpResponse
转换为
ServletServerHttpResponse
,您就可以了

(必须是
ServletServerHttpResponse
根据
ResponseByAdvice
的调用方式,您可以看到传递给
ResponseByAdvice
ServerHttpResponse
实际上是
ServletServerHttpResponse

因此,只需实现一个
ResponseBodyAdvice
,无需再包装
HttpServletResponse

@ControllerAdvice
public class FooBodyAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if(response instanceof  ServletServerHttpResponse) {

            ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
            res.getServletResponse().getStatus(); //get the status code

            res.getHeaders().set("fooHeader", "fooValue"); //modify headers
            res.getHeaders().setETag("33a64df551425fcc55e4d42a148795d9f25f89d4") //use "type safe" methods to modify header 
        }

        return body;
    }
}

你说的是servlet过滤器。您在链中的何处添加了过滤器?你能不能快速检查一下对你有用?我试过了。我将在filterChain.doChain()之后添加标题,因为我需要链完成后的状态代码信息。它不起作用,Spring提交响应并且不允许在过滤器中进行修改。这与Spring无关,但是纯java一旦响应已经(部分)发送到客户端,就不能再修改头信息。你只能在那之前改变它。但我想你是自己还401,那么为什么不干脆在那个地方还呢?它在不同的地方还。文档声明修改标题不可能在postHandle中,而是在其他地方,我正在寻找那个地方以及如何做到这一点。在
preHandle
中。一旦代码发送完毕,就再也无法对其进行任何处理。代码库中到处都是这种味道,不是有点难闻吗?难道不应该在单个位置执行安全性操作吗?您有包装响应对象的示例吗?正如Ian Roberts在下面所述:您还必须覆盖
sendError
方法和附加(不推荐的)
setStatus
消息。只是为了确保捕获所有案例。@M.Denium是的,正如我在代码中的评论中所指出的那样。这些方法的实现留给读者作为练习…谢谢你的回答,这解决了我在alfresco Stacks上的问题。如果他们不调用setStatus()怎么办?因为当你用对象响应时,默认设置是200?
@ControllerAdvice
public class FooBodyAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if(response instanceof  ServletServerHttpResponse) {

            ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
            res.getServletResponse().getStatus(); //get the status code

            res.getHeaders().set("fooHeader", "fooValue"); //modify headers
            res.getHeaders().setETag("33a64df551425fcc55e4d42a148795d9f25f89d4") //use "type safe" methods to modify header 
        }

        return body;
    }
}