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;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
|
@ -20,68 +22,137 @@ package ca.uhn.fhir.rest.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
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>
|
||||
* 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
|
||||
* <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)
|
||||
* 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 <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>
|
||||
* 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>
|
||||
* 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>
|
||||
*
|
||||
* @author Created by Bill de Beaubien on 3/30/2015.
|
||||
*/
|
||||
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) {
|
||||
myUseHttps = theUseHttps;
|
||||
private static final Logger LOG = LoggerFactory
|
||||
.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
|
||||
public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) {
|
||||
String forwardedHost = getForwardedHost(theRequest);
|
||||
if (forwardedHost != null) {
|
||||
return forwardedServerBase(theServletContext, theRequest, forwardedHost);
|
||||
}
|
||||
return super.determineServerBase(theServletContext, theRequest);
|
||||
public String determineServerBase(ServletContext servletContext,
|
||||
HttpServletRequest request) {
|
||||
String serverBase = super.determineServerBase(servletContext, request);
|
||||
ServletServerHttpRequest requestWrapper = new ServletServerHttpRequest(
|
||||
request);
|
||||
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) {
|
||||
String serverBase = super.determineServerBase(theServletContext, theRequest);
|
||||
String host = theRequest.getHeader("host");
|
||||
if (host != null) {
|
||||
serverBase = serverBase.replace(host, theForwardedHost);
|
||||
serverBase = serverBase.substring(serverBase.indexOf("://"));
|
||||
return protocol(theRequest) + serverBase;
|
||||
}
|
||||
return serverBase;
|
||||
private String forwardedServerBase(String originalServerBase,
|
||||
HttpHeaders headers, String forwardedHost) {
|
||||
Optional<String> forwardedPrefix = getForwardedPrefix(headers);
|
||||
LOG.debug("serverBase: {}, forwardedHost: {}, forwardedPrefix: {}",
|
||||
originalServerBase, forwardedHost, forwardedPrefix);
|
||||
LOG.debug("request header: {}", headers);
|
||||
|
||||
String host = protocol(headers) + "://" + forwardedHost;
|
||||
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) {
|
||||
String forwardedHost = theRequest.getHeader("x-forwarded-host");
|
||||
if (forwardedHost != null) {
|
||||
int commaPos = forwardedHost.indexOf(',');
|
||||
if (commaPos >= 0) {
|
||||
forwardedHost = forwardedHost.substring(0, commaPos - 1);
|
||||
}
|
||||
}
|
||||
return forwardedHost;
|
||||
private Optional<String> port(HttpHeaders headers) {
|
||||
return ofNullable(headers.getFirst(X_FORWARDED_PORT));
|
||||
}
|
||||
|
||||
protected String protocol(HttpServletRequest theRequest) {
|
||||
String protocol = theRequest.getHeader("x-forwarded-proto");
|
||||
private String pathFrom(String serverBase) {
|
||||
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) {
|
||||
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