Spring 引导替换 Filter 中的 ServletException 响应

Spring Boot replace ServletException response in Filter

我有一个 Spring 引导过滤器,我正在使用它来使用 Jwt 进行身份验证。如果成功,一切都会很好,我会发出 Json 对我的设计的回应。但是,如果 Authorization header 丢失或不正确,我会抛出带有自定义消息的 ServletException。这会导致丑陋的 Json 看起来像这样:

{
   "timestamp":1453192910756,
   "status":500,
   "error":"Internal Server Error",
   "exception":"javax.servlet.ServletException",
   "message":"Invalid Authorization header.",
   "path":"/api/test"
}

我希望自定义此 Json,使其采用我用于所有其他回复的标准格式。

我的过滤器代码在这里:

public class JwtFilter extends GenericFilterBean {
    @Override
    public void doFilter(final ServletRequest req,
                         final ServletResponse res,
                         final FilterChain chain) throws IOException, ServletException {
        System.out.println("JwtFilter");
        final HttpServletRequest request = (HttpServletRequest) req;
        final String authHeader = request.getHeader("Authorization");
        if (authHeader == null) {
            throw new ServletException("Missing Authorization header.");
        }
        if (!authHeader.startsWith("Bearer ")) {
            throw new ServletException("Invalid Authorization header.");
        }
        final String token = authHeader.substring(7);
        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey")
                    .parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        }
        catch (final SignatureException e) {
            throw new ServletException("Invalid token.");
        }
        chain.doFilter(req, res);
    }
}

我尝试使用包装器来包装响应,但这没有用。另一个 SO post 说响应是不可更改的,但这甚至没有意义。

我认为正确的方法是编辑 ServletResponse res 但我无法让它工作。

谢谢!

编辑: 有点老套,但确实有效。如果有更好的方法,请回答:

public class JwtFilter extends GenericFilterBean {
    @Override
    public void doFilter(final ServletRequest req,
                         final ServletResponse res,
                         final FilterChain chain) throws IOException, ServletException {
        System.out.println("JwtFilter");
        final HttpServletRequest request = (HttpServletRequest) req;
        final String authHeader = request.getHeader("Authorization");
        if (authHeader == null) {
            res.setContentType("application/json;charset=UTF-8");
            res.getWriter().write(ExceptionCreator.createJson("Missing Authorization header."));
            return;
        }
        if (!authHeader.startsWith("Bearer ")) {
            res.setContentType("application/json;charset=UTF-8");
            res.getWriter().write(ExceptionCreator.createJson("Invalid Authorization header."));
            return;
        }
        final String token = authHeader.substring(7);
        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey")
                    .parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        }
        catch (Exception f) {
            res.setContentType("application/json;charset=UTF-8");
            res.getWriter().write(ExceptionCreator.createJson("Invalid token."));
            return;
        }
        chain.doFilter(req, res);
    }
}

一般来说,包装响应然后在调用doFilter之后修改响应输出流是正确的做法,例如

PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper(
        (HttpServletResponse)response);
chain.doFilter(request, wrapper);
CharArrayWriter caw = new CharArrayWriter();
caw.write("your json");
response.setContentLength(caw.toString().getBytes().length);
out.write(caw.toString());
out.close();

取自Oracle JavaEE 5 Tutorial

然而,您的用例似乎更适合在 RestController 处理程序方法中处理,可能与 @ExceptionHandler(ServletException.class) 注释方法结合使用。这将是一种更通用的方法,允许您利用 Spring 内容协商的力量来处理 JSON 序列化。