spring-cloud-netflix: Zuul returning 400 bad request when changing body with HttpServletRequestWrapper

Personal, the zuul receives a request with the encrypted body, decrypts the body and passes the request to the internal services with the payload open.

It does the right job, however I have identified a bug, the first call works, the second gives 400 error, the third one works, the fourth gives 400, and so on, that is, one request works, the other does not.

public class SimpleFilter extends ZuulFilter {

	private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
	private UrlPathHelper urlPathHelper = new UrlPathHelper();
	
	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 1;
	}

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

	@Override
	public Object run() {

		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());

		if (RequestMethod.valueOf(request.getMethod()) == RequestMethod.POST)
			ctx.setRequest(DecryptionFilter.getInstance().parse(request));

		return null;
	}
}
public class DecryptionFilter {

	private static DecryptionFilter instance;
	private JsonObject json;
	private String body;

	public HttpServletRequest parse(HttpServletRequest request) {

		try {
			InputStream is = request.getInputStream();
			this.body = IOUtils.toString(is);
		} catch (IOException e) {
			e.printStackTrace();
		}

		this.json = new JsonParser().parse(body).getAsJsonObject();

		try {
			this.body = Cryptography.decrypt(new Param(
					json.get("param").getAsString(), 
					json.get("param1").getAsString()), "src/main/resources/private.pem");
		} catch (Exception e) {
			throw new RuntimeException();
		}
		
		return modifyRequest(request, body);
	}
	
	private static HttpServletRequestWrapper modifyRequest(HttpServletRequest request, String body) {
		System.out.println("body: " + body);
		return new HttpServletRequestWrapper(request) {
			
			@Override
			public ServletInputStream getInputStream() {
				return new ServletInputStreamWrapper(body.getBytes());
			}

		};
	}
	
	public static DecryptionFilter getInstance() {
		if (instance == null)
			instance = new DecryptionFilter();
		return instance;
	}

}
buildscript {
	ext {
		springBootVersion = '1.5.6.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
		classpath "se.transmode.gradle:gradle-docker:1.2"
		classpath('io.spring.gradle:dependency-management-plugin:0.5.4.RELEASE')
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'docker'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
	mavenCentral()
}

jar {
	baseName = 'app'
	version = '0.0.1-SNAPSHOT'
	archiveName = 'app.jar'
}

ext {
	springCloudVersion = 'Dalston.SR2'
}

dependencies {
	compile('org.springframework.cloud:spring-cloud-starter-zuul')
	compile('org.springframework.boot:spring-boot-starter-web')
	compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
	compile('org.springframework.cloud:spring-cloud-starter-config')
	compile('org.springframework.cloud:spring-cloud-starter-bus-amqp')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 31 (6 by maintainers)

Most upvoted comments

@ryanjbaxter This is a good example but another right way using the HtttpServletRequestWrapper as @eutiagocosta used on your sample as follows.

 private static HttpServletRequestWrapper modifyRequest(HttpServletRequest request, String body) {

    HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
        @Override
        public byte[] getContentData() {           	
            byte[] data = null;
            try {
                data = body.getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return data;
        }

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

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

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(body.getBytes("UTF-8"))));
        }

        @Override
        public ServletInputStream getInputStream() throws UnsupportedEncodingException {
            return new ServletInputStreamWrapper(body.getBytes("UTF-8"));
        }
    };

    return wrapper;
}

@eutiagocosta the issue with HttpServletRequestWrapper occurs when you change request content length. In other words, when you change the request content you need rewrite the other descriptors as content length and other reader methods from your request.

package your_organization.cloud.agent.filters;

import com.netflix.zuul.http.ServletInputStreamWrapper;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class YourRequestWrapper extends HttpServletRequestWrapper {
    public YourRequestWrapper  (HttpServletRequest request, byte[] data)
    {
        super(request);
        dataBytes = data;
    }

    public YourRequestWrapper (HttpServletRequest request, String body)
    {
        super(request);
        try {
            dataBytes = body.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private byte[] dataBytes;
    //@Override
    public byte[] getContentData() {
        return dataBytes;
    }

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

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

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(dataBytes)));
    }

    @Override
    public ServletInputStream getInputStream() throws UnsupportedEncodingException {
        return new ServletInputStreamWrapper(dataBytes);
    }
}
package your_organization.cloud.agent.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import javax.servlet.http.*;
import java.io.*;
import java.nio.charset.Charset;

import static com.netflix.zuul.context.RequestContext.getCurrentContext;
import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;

@Component
public class EncryptFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 20;
    }

    @Override
    public boolean shouldFilter() {
        HttpServletRequest request = getCurrentContext().getRequest();
        boolean wsdlRequest = request.getRequestURI().toLowerCase().endsWith(".wsdl");
        boolean postSoap = request.getMethod().equalsIgnoreCase("post") && request.getContentType().toLowerCase().startsWith("text/xml");
        return wsdlRequest || postSoap;
    }

    @Override
    public Object run() {

        try {
            RequestContext context = getCurrentContext();
            InputStream in = (InputStream) context.get("requestEntity");
            if (in == null) {
                in = context.getRequest().getInputStream();
            }

            String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
            context.setRequest(new YourRequestWrapper(context.getRequest(), new String(Base64.encodeBase64(body.getBytes()))));
        }
        catch (IOException e) {
            rethrowRuntimeException(e);
        }
        return null;
    }
}