Manejo de Excepciones: getReader() ya fue invocado en la solicitud HTTP

Al implementar un filtro personalizado en Spring Boot para validación de firmas en el cuerpo de solicitudes, surge un conflicto cuando múltiples componentes intentan acceder al contenido del request. Este escenario es común en sistemas que combinan interceptores de seguridad y aspetcos de registro.

El error java.lang.IllegalStateException: getReader() has already been called for this request ocurre porque los métodos getInputStream() y getReader() de HttpServletRequest consumen el flujo de datos de forma irreversible. Al ser implementados como streams de bajo nivel, su lectura es unidireccional y no reutilizalbe.

La solución implica crear un wrapper que almacene el contenido del cuerpo durante la primera lectura. Utilizando HttpServletRequestWrapper, podemos interceptar y cachear el flujo antes de que otros componentes lo procesen:

import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.ServletInputStream;
import javax.servlet.ReadListener;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class RequestCacheWrapper extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    public RequestCacheWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = request.getInputStream().readAllBytes();
    }

    public String obtenerCuerpoSolicitud() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }

    @Override
    public ServletInputStream getInputStream() {
        ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public int read() {
                return byteArrayStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                throw new UnsupportedOperationException();
            }
        };
    }
}

En la implementación del filtro, se debe sustituir la solicitud original:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
    throws IOException, ServletException {
    
    RequestCacheWrapper wrappedRequest = new RequestCacheWrapper((HttpServletRequest) request);
    String requestBody = wrappedRequest.obtenerCuerpoSolicitud();
    
    if (!validarFirma(requestBody)) {
        throw new FirmaInvalidaException();
    }
    
    chain.doFilter(wrappedRequest, response);
}

Este enfoque garantiza que cualquier comopnente posterior (interceptores, aspectos de logging) acceda al cuerpo de la solicitud mediante el wrapper, evitando la excepción al reutilizar el flujo almacenado en memoria.

Etiquetas: spring-boot httpServletRequest filtro firmas flujo

Publicado el 8-21 22:39