Extend support for additional forwared headers x-forwarded-port and (#1788)
x-forwarded-prefix
This commit is contained in:
parent
e219c1774b
commit
ea817de68a
|
@ -1,5 +1,7 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Server Framework
|
* HAPI FHIR - Server Framework
|
||||||
|
@ -20,68 +22,137 @@ package ca.uhn.fhir.rest.server;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Works like the normal {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's an x-forwarded-host present, in which case that's used in place of the server's address.
|
* Works like the normal
|
||||||
|
* {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's
|
||||||
|
* an x-forwarded-host present, in which case that's used in place of the
|
||||||
|
* server's address.
|
||||||
* <p>
|
* <p>
|
||||||
* If the Apache Http Server <code>mod_proxy</code> isn't configured to supply <code>x-forwarded-proto</code>, the factory method that you use to create the address strategy will determine the default. Note that
|
* If the Apache Http Server <code>mod_proxy</code> isn't configured to supply
|
||||||
* <code>mod_proxy</code> doesn't set this by default, but it can be configured via <code>RequestHeader set X-Forwarded-Proto http</code> (or https)
|
* <code>x-forwarded-proto</code>, the factory method that you use to create the
|
||||||
|
* address strategy will determine the default. Note that <code>mod_proxy</code>
|
||||||
|
* doesn't set this by default, but it can be configured via
|
||||||
|
* <code>RequestHeader set X-Forwarded-Proto http</code> (or https)
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* If you want to set the protocol based on something other than the constructor argument, you should be able to do so by overriding <code>protocol</code>.
|
* List of supported forward headers:
|
||||||
|
* <ul>
|
||||||
|
* <li>x-forwarded-host - original host requested by the client throw proxy
|
||||||
|
* server
|
||||||
|
* <li>x-forwarded-proto - original protocol (http, https) requested by the
|
||||||
|
* client
|
||||||
|
* <li>x-forwarded-port - original port request by the client, assume default
|
||||||
|
* port if not defined
|
||||||
|
* <li>x-forwarded-prefix - original server prefix / context path requested by
|
||||||
|
* the client
|
||||||
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Note that while this strategy was designed to work with Apache Http Server, and has been tested against it, it should work with any proxy server that sets <code>x-forwarded-host</code>
|
* If you want to set the protocol based on something other than the constructor
|
||||||
|
* argument, you should be able to do so by overriding <code>protocol</code>.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that while this strategy was designed to work with Apache Http Server,
|
||||||
|
* and has been tested against it, it should work with any proxy server that
|
||||||
|
* sets <code>x-forwarded-host</code>
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Created by Bill de Beaubien on 3/30/2015.
|
|
||||||
*/
|
*/
|
||||||
public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
|
public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
|
||||||
private boolean myUseHttps = false;
|
private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
|
||||||
|
private static final String X_FORWARDED_PROTO = "x-forwarded-proto";
|
||||||
|
private static final String X_FORWARDED_HOST = "x-forwarded-host";
|
||||||
|
private static final String X_FORWARDED_PORT = "x-forwarded-port";
|
||||||
|
|
||||||
protected ApacheProxyAddressStrategy(boolean theUseHttps) {
|
private static final Logger LOG = LoggerFactory
|
||||||
myUseHttps = theUseHttps;
|
.getLogger(ApacheProxyAddressStrategy.class);
|
||||||
|
|
||||||
|
private final boolean useHttps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param useHttps
|
||||||
|
* Is used when the {@code x-forwarded-proto} is not set in the
|
||||||
|
* request.
|
||||||
|
*/
|
||||||
|
public ApacheProxyAddressStrategy(boolean useHttps) {
|
||||||
|
this.useHttps = useHttps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) {
|
public String determineServerBase(ServletContext servletContext,
|
||||||
String forwardedHost = getForwardedHost(theRequest);
|
HttpServletRequest request) {
|
||||||
if (forwardedHost != null) {
|
String serverBase = super.determineServerBase(servletContext, request);
|
||||||
return forwardedServerBase(theServletContext, theRequest, forwardedHost);
|
ServletServerHttpRequest requestWrapper = new ServletServerHttpRequest(
|
||||||
}
|
request);
|
||||||
return super.determineServerBase(theServletContext, theRequest);
|
HttpHeaders headers = requestWrapper.getHeaders();
|
||||||
|
Optional<String> forwardedHost = headers
|
||||||
|
.getValuesAsList(X_FORWARDED_HOST).stream().findFirst();
|
||||||
|
return forwardedHost
|
||||||
|
.map(s -> forwardedServerBase(serverBase, headers, s))
|
||||||
|
.orElse(serverBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String forwardedServerBase(ServletContext theServletContext, HttpServletRequest theRequest, String theForwardedHost) {
|
private String forwardedServerBase(String originalServerBase,
|
||||||
String serverBase = super.determineServerBase(theServletContext, theRequest);
|
HttpHeaders headers, String forwardedHost) {
|
||||||
String host = theRequest.getHeader("host");
|
Optional<String> forwardedPrefix = getForwardedPrefix(headers);
|
||||||
if (host != null) {
|
LOG.debug("serverBase: {}, forwardedHost: {}, forwardedPrefix: {}",
|
||||||
serverBase = serverBase.replace(host, theForwardedHost);
|
originalServerBase, forwardedHost, forwardedPrefix);
|
||||||
serverBase = serverBase.substring(serverBase.indexOf("://"));
|
LOG.debug("request header: {}", headers);
|
||||||
return protocol(theRequest) + serverBase;
|
|
||||||
}
|
String host = protocol(headers) + "://" + forwardedHost;
|
||||||
return serverBase;
|
String hostWithOptionalPort = port(headers).map(p -> (host + ":" + p))
|
||||||
|
.orElse(host);
|
||||||
|
|
||||||
|
String path = forwardedPrefix
|
||||||
|
.orElseGet(() -> pathFrom(originalServerBase));
|
||||||
|
return joinStringsWith(hostWithOptionalPort, path, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getForwardedHost(HttpServletRequest theRequest) {
|
private Optional<String> port(HttpHeaders headers) {
|
||||||
String forwardedHost = theRequest.getHeader("x-forwarded-host");
|
return ofNullable(headers.getFirst(X_FORWARDED_PORT));
|
||||||
if (forwardedHost != null) {
|
|
||||||
int commaPos = forwardedHost.indexOf(',');
|
|
||||||
if (commaPos >= 0) {
|
|
||||||
forwardedHost = forwardedHost.substring(0, commaPos - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return forwardedHost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String protocol(HttpServletRequest theRequest) {
|
private String pathFrom(String serverBase) {
|
||||||
String protocol = theRequest.getHeader("x-forwarded-proto");
|
String serverBasePath = URI.create(serverBase).getPath();
|
||||||
|
return StringUtils.defaultIfBlank(serverBasePath, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinStringsWith(String left, String right,
|
||||||
|
String joiner) {
|
||||||
|
if (left.endsWith(joiner) && right.startsWith(joiner)) {
|
||||||
|
return left + right.substring(1);
|
||||||
|
} else if (left.endsWith(joiner) || right.startsWith(joiner)) {
|
||||||
|
return left + right;
|
||||||
|
} else {
|
||||||
|
return left + joiner + right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getForwardedPrefix(HttpHeaders headers) {
|
||||||
|
return ofNullable(headers.getFirst(X_FORWARDED_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String protocol(HttpHeaders headers) {
|
||||||
|
String protocol = headers.getFirst(X_FORWARDED_PROTO);
|
||||||
if (protocol != null) {
|
if (protocol != null) {
|
||||||
return protocol;
|
return protocol;
|
||||||
}
|
}
|
||||||
return myUseHttps ? "https" : "http";
|
return useHttps ? "https" : "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
|
||||||
|
public class ApacheProxyAddressStrategyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutForwarded() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://localhost/imagingstudy/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwardedHostWithoutForwardedProtoHttps() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwardedHostWithoutForwardedProtoHttp() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
false);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("http://my.example.host/imagingstudy/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwarded() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
request.addHeader("X-Forwarded-Proto", "https");
|
||||||
|
request.addHeader("X-Forwarded-Prefix", "server-prefix/fhir");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwardedWithHostPrefixWithSlash() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
request.addHeader("host", "localhost");
|
||||||
|
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
request.addHeader("X-Forwarded-Proto", "https");
|
||||||
|
request.addHeader("X-Forwarded-Prefix", "/server-prefix/fhir");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwardedWithoutPrefix() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
request.addHeader("X-Forwarded-Proto", "https");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithForwardedHostAndPort() {
|
||||||
|
ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
|
||||||
|
true);
|
||||||
|
MockHttpServletRequest request = prepareRequest();
|
||||||
|
|
||||||
|
request.addHeader("X-Forwarded-Host", "my.example.host");
|
||||||
|
request.addHeader("X-Forwarded-Port", "345");
|
||||||
|
String serverBase = addressStrategy.determineServerBase(null, request);
|
||||||
|
assertEquals("https://my.example.host:345/imagingstudy/fhir",
|
||||||
|
serverBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest prepareRequest() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setMethod("POST");
|
||||||
|
request.setScheme("https");
|
||||||
|
request.setServerPort(443);
|
||||||
|
request.setServletPath("/fhir");
|
||||||
|
request.setServerName("localhost");
|
||||||
|
request.setRequestURI("/imagingstudy/fhir/imagingstudy?_format=json");
|
||||||
|
request.setContextPath("/imagingstudy");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue