From a38b7b71ac8be9608ac2530dac41cd6298d696cf Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 25 Oct 2016 12:56:49 +0100 Subject: [PATCH] Append to X-Forwarded-* headers instead of replacing them This fixes most of the issues people encounter when there are multiple proxies in the request. The tricky thing is that there is another header "Forwarded" that we don't recognize, but which backends probably do, at least some of the time (since it is from an actual RFC). The problem is that "Forwarded" does not contain the ports, so Spring UriComponentsBuilder cannot use it to rewrite links to a specific port. Since we do not support it already, this change doesn't make things any worse, but the corner case is there still. --- .../zuul/filters/pre/PreDecorationFilter.java | 30 +++++++++++++++++-- .../filters/pre/PreDecorationFilterTests.java | 17 +++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java index 1b5bd474..1f65218f 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -163,15 +163,39 @@ public class PreDecorationFilter extends ZuulFilter { } private void addProxyHeaders(RequestContext ctx, Route route) { - String host = toHostHeader(ctx.getRequest()); - String port = String.valueOf(ctx.getRequest().getServerPort()); - String proto = ctx.getRequest().getScheme(); + HttpServletRequest request = ctx.getRequest(); + String host = toHostHeader(request); + String port = String.valueOf(request.getServerPort()); + String proto = request.getScheme(); + if (hasHeader(request, "X-Forwarded-Host")) { + host = request.getHeader("X-Forwarded-Host") + "," + host; + if (!hasHeader(request, "X-Forwarded-Port")) { + if (hasHeader(request, "X-Forwarded-Proto")) { + StringBuilder builder = new StringBuilder(); + for (String previous : StringUtils.commaDelimitedListToStringArray(request.getHeader("X-Forwarded-Proto"))) { + if (builder.length()>0) { + builder.append(","); + } + builder.append("https".equals(previous) ? "443" : "80"); + } + builder.append(",").append(port); + port = builder.toString(); + } + } else { + port = request.getHeader("X-Forwarded-Port") + "," + port; + } + proto = request.getHeader("X-Forwarded-Proto") + "," + proto; + } ctx.addZuulRequestHeader("X-Forwarded-Host", host); ctx.addZuulRequestHeader("X-Forwarded-Port", port); ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO, proto); addProxyPrefix(ctx, route); } + private boolean hasHeader(HttpServletRequest request, String name) { + return StringUtils.hasLength(request.getHeader(name)); + } + private void addProxyPrefix(RequestContext ctx, Route route) { String forwardedPrefix = ctx.getRequest().getHeader("X-Forwarded-Prefix"); String contextPath = ctx.getRequest().getContextPath(); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java index 940634ff..b07b43a2 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java @@ -103,6 +103,23 @@ public class PreDecorationFilterTests { assertEquals("localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host")); } + @Test + public void xForwardedHostAppends() throws Exception { + this.properties.setPrefix("/api"); + this.request.setRequestURI("/api/foo/1"); + this.request.setRemoteAddr("5.6.7.8"); + this.request.setServerPort(8080); + this.request.addHeader("X-Forwarded-Host", "example.com"); + this.request.addHeader("X-Forwarded-Proto", "https"); + this.routeLocator.addRoute( + new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null)); + this.filter.run(); + RequestContext ctx = RequestContext.getCurrentContext(); + assertEquals("example.com,localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host")); + assertEquals("443,8080", ctx.getZuulRequestHeaders().get("x-forwarded-port")); + assertEquals("https,http", ctx.getZuulRequestHeaders().get("x-forwarded-proto")); + } + @Test public void hostHeaderSet() throws Exception { this.properties.setPrefix("/api");