admin管理员组

文章数量:1353303

I'm developing a Spring Boot application (with Spring Security) and need to add default headers (specifically, Content-Type and Content-Length) to requests targeting the endpoint /api/v1/mpos/set-token when those headers are missing. To achieve this, I wrote a custom filter that extends OncePerRequestFilter and wraps the incoming HttpServletRequest so that I can read and cache its body. I use the following code:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SetTokenDefaultHeadersFilter extends OncePerRequestFilter {

    private static final String DEFAULT_CONTENT_TYPE = "application/json";
    private static final String TARGET_URI = "/api/v1/mpos/set-token";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (TARGET_URI.equals(request.getRequestURI())
                && "POST".equalsIgnoreCase(request.getMethod())
                && (request.getHeader("Content-Type") == null || request.getHeader("Content-Length") == null)) {

            HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
                private final byte[] cachedBody = toByteArray(request.getInputStream());

                private byte[] toByteArray(InputStream input) throws IOException {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = input.read(buffer)) != -1) {
                        baos.write(buffer, 0, len);
                    }
                    return baos.toByteArray();
                }

                @Override
                public ServletInputStream getInputStream() {
                    final ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody);
                    return new ServletInputStream() {
                        @Override
                        public int read() {
                            return bais.read();
                        }
                        @Override
                        public boolean isFinished() {
                            return bais.available() == 0;
                        }
                        @Override
                        public boolean isReady() {
                            return true;
                        }
                        @Override
                        public void setReadListener(ReadListener readListener) {
                            // Not implemented
                        }
                    };
                }

                @Override
                public BufferedReader getReader() {
                    return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
                }

                @Override
                public int getContentLength() {
                    return cachedBody.length;
                }

                @Override
                public long getContentLengthLong() {
                    return cachedBody.length;
                }

                @Override
                public String getHeader(String name) {
                    if ("Content-Type".equalsIgnoreCase(name)) {
                        String header = super.getHeader(name);
                        return header == null ? DEFAULT_CONTENT_TYPE : header;
                    }
                    if ("Content-Length".equalsIgnoreCase(name)) {
                        String header = super.getHeader(name);
                        return header == null ? String.valueOf(cachedBody.length) : header;
                    }
                    return super.getHeader(name);
                }
            };

            filterChain.doFilter(wrappedRequest, response);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

I have verified (via logging and FilterRegistrationBean) that my filter is registered with the highest precedence and should run first. However, when I call input.read(buffer), it immediately returns -1 (i.e. EOF), and the cached body is an empty array.

What I've tried so far:

Verified the filter order – my filter is running first.

Checked for other filters that might be reading the body; the logs indicate that my filter is before them.

My questions:

What could be consuming the request body before my filter even gets a chance to read it?

How can I ensure that my filter has access to the full request body so I can wrap it and add the default headers?

Is there a recommended approach in Spring Boot to cache the request body before any processing?

Any insights or suggestions would be greatly appreciated.

本文标签: javaCustom OncePerRequestFilter reads empty request body – why is the body already consumedStack Overflow