org.eclipse.jetty.server.ForwardedRequestCustomizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-server Show documentation
Show all versions of jetty-server Show documentation
The core jetty server artifact.
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.net.InetSocketAddress;
import javax.servlet.ServletRequest;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Customize Requests for Proxy Forwarding.
*
* This customizer looks at at HTTP request for headers that indicate
* it has been forwarded by one or more proxies. Specifically handled are
*
* - {@code Forwarded}, as defined by rfc7239
*
- {@code X-Forwarded-Host}
* - {@code X-Forwarded-Server}
* - {@code X-Forwarded-For}
* - {@code X-Forwarded-Proto}
* - {@code X-Proxied-Https}
*
* If these headers are present, then the {@link Request} object is updated
* so that the proxy is not seen as the other end point of the connection on which
* the request came
* Headers can also be defined so that forwarded SSL Session IDs and Cipher
* suites may be customised
* @see Wikipedia: X-Forwarded-For
*/
public class ForwardedRequestCustomizer implements Customizer
{
private static final Logger LOG = Log.getLogger(ForwardedRequestCustomizer.class);
private HostPortHttpField _forcedHost;
private String _forwardedHeader = HttpHeader.FORWARDED.toString();
private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
private String _forwardedHttpsHeader = "X-Proxied-Https";
private String _forwardedCipherSuiteHeader = "Proxy-auth-cert";
private String _forwardedSslSessionIdHeader = "Proxy-ssl-id";
private boolean _proxyAsAuthority=false;
private boolean _sslIsSecure=true;
/**
* @return true if the proxy address obtained via
* {@code X-Forwarded-Server} or RFC7239 "by" is used as
* the request authority. Default false
*/
public boolean getProxyAsAuthority()
{
return _proxyAsAuthority;
}
/**
* @param proxyAsAuthority if true, use the proxy address obtained via
* {@code X-Forwarded-Server} or RFC7239 "by" as the request authority.
*/
public void setProxyAsAuthority(boolean proxyAsAuthority)
{
_proxyAsAuthority = proxyAsAuthority;
}
/**
* @param rfc7239only Configure to only support the RFC7239 Forwarded header and to
* not support any {@code X-Forwarded-} headers. This convenience method
* clears all the non RFC headers if passed true and sets them to
* the default values (if not already set) if passed false.
*/
public void setForwardedOnly(boolean rfc7239only)
{
if (rfc7239only)
{
if (_forwardedHeader==null)
_forwardedHeader=HttpHeader.FORWARDED.toString();
_forwardedHostHeader=null;
_forwardedHostHeader=null;
_forwardedServerHeader=null;
_forwardedForHeader=null;
_forwardedProtoHeader=null;
_forwardedHttpsHeader=null;
}
else
{
if (_forwardedHostHeader==null)
_forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
if (_forwardedServerHeader==null)
_forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
if (_forwardedForHeader==null)
_forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
if (_forwardedProtoHeader==null)
_forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
if (_forwardedHttpsHeader==null)
_forwardedHttpsHeader = "X-Proxied-Https";
}
}
public String getForcedHost()
{
return _forcedHost.getValue();
}
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
*
* @param hostAndPort
* The value of the host header to force.
*/
public void setForcedHost(String hostAndPort)
{
_forcedHost = new HostPortHttpField(hostAndPort);
}
/**
* @return The header name for RFC forwarded (default Forwarded)
*/
public String getForwardedHeader()
{
return _forwardedHeader;
}
/**
* @param forwardedHeader
* The header name for RFC forwarded (default Forwarded)
*/
public void setForwardedHeader(String forwardedHeader)
{
_forwardedHeader = forwardedHeader;
}
public String getForwardedHostHeader()
{
return _forwardedHostHeader;
}
/**
* @param forwardedHostHeader
* The header name for forwarded hosts (default {@code X-Forwarded-Host})
*/
public void setForwardedHostHeader(String forwardedHostHeader)
{
_forwardedHostHeader = forwardedHostHeader;
}
/**
* @return the header name for forwarded server.
*/
public String getForwardedServerHeader()
{
return _forwardedServerHeader;
}
/**
* @param forwardedServerHeader
* The header name for forwarded server (default {@code X-Forwarded-Server})
*/
public void setForwardedServerHeader(String forwardedServerHeader)
{
_forwardedServerHeader = forwardedServerHeader;
}
/**
* @return the forwarded for header
*/
public String getForwardedForHeader()
{
return _forwardedForHeader;
}
/**
* @param forwardedRemoteAddressHeader
* The header name for forwarded for (default {@code X-Forwarded-For})
*/
public void setForwardedForHeader(String forwardedRemoteAddressHeader)
{
_forwardedForHeader = forwardedRemoteAddressHeader;
}
/**
* Get the forwardedProtoHeader.
*
* @return the forwardedProtoHeader (default {@code X-Forwarded-Proto})
*/
public String getForwardedProtoHeader()
{
return _forwardedProtoHeader;
}
/**
* Set the forwardedProtoHeader.
*
* @param forwardedProtoHeader
* the forwardedProtoHeader to set (default {@code X-Forwarded-Proto})
*/
public void setForwardedProtoHeader(String forwardedProtoHeader)
{
_forwardedProtoHeader = forwardedProtoHeader;
}
/**
* @return The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
*/
public String getForwardedCipherSuiteHeader()
{
return _forwardedCipherSuiteHeader;
}
/**
* @param forwardedCipherSuite
* The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
*/
public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
{
_forwardedCipherSuiteHeader = forwardedCipherSuite;
}
/**
* @return The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
*/
public String getForwardedSslSessionIdHeader()
{
return _forwardedSslSessionIdHeader;
}
/**
* @param forwardedSslSessionId
* The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
*/
public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
{
_forwardedSslSessionIdHeader = forwardedSslSessionId;
}
/**
* @return The header name holding a forwarded Https status indicator (on|off true|false) (default {@code X-Proxied-Https})
*/
public String getForwardedHttpsHeader()
{
return _forwardedHttpsHeader;
}
/**
* @param forwardedHttpsHeader the header name holding a forwarded Https status indicator(default {@code X-Proxied-Https})
*/
public void setForwardedHttpsHeader(String forwardedHttpsHeader)
{
_forwardedHttpsHeader = forwardedHttpsHeader;
}
/**
* @return true if the presence of a SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public boolean isSslIsSecure()
{
return _sslIsSecure;
}
/**
* @param sslIsSecure true if the presence of a SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public void setSslIsSecure(boolean sslIsSecure)
{
_sslIsSecure = sslIsSecure;
}
@Override
public void customize(Connector connector, HttpConfiguration config, Request request)
{
HttpFields httpFields = request.getHttpFields();
RFC7239 rfc7239 = null;
String forwardedHost = null;
String forwardedServer = null;
HostPort forwardedFor = null;
String forwardedProto = null;
String forwardedHttps = null;
// Do a single pass through the header fields as it is a more efficient single iteration.
for (HttpField field : httpFields)
{
String name = field.getName();
if (getForwardedCipherSuiteHeader()!=null && getForwardedCipherSuiteHeader().equalsIgnoreCase(name))
{
request.setAttribute("javax.servlet.request.cipher_suite",field.getValue());
if (isSslIsSecure())
{
request.setSecure(true);
request.setScheme(config.getSecureScheme());
}
}
if (getForwardedSslSessionIdHeader()!=null && getForwardedSslSessionIdHeader().equalsIgnoreCase(name))
{
request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
if (isSslIsSecure())
{
request.setSecure(true);
request.setScheme(config.getSecureScheme());
}
}
if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name))
forwardedHost = getLeftMost(field.getValue());
if (forwardedServer==null && _forwardedServerHeader!=null && _forwardedServerHeader.equalsIgnoreCase(name))
forwardedServer = getLeftMost(field.getValue());
if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name))
forwardedFor = getRemoteAddr(field.getValue());
if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name))
forwardedProto = getLeftMost(field.getValue());
if (forwardedHttps==null && _forwardedHttpsHeader!=null && _forwardedHttpsHeader.equalsIgnoreCase(name))
forwardedHttps = getLeftMost(field.getValue());
if (_forwardedHeader!=null && _forwardedHeader.equalsIgnoreCase(name))
{
if (rfc7239==null)
rfc7239= new RFC7239();
rfc7239.addValue(field.getValue());
}
}
// Handle host header if if not available any RFC7230.by or X-ForwardedServer header
if (_forcedHost != null)
{
// Update host header
httpFields.put(_forcedHost);
request.setAuthority(_forcedHost.getHost(),_forcedHost.getPort());
}
else if (rfc7239!=null && rfc7239._host!=null)
{
HostPortHttpField auth = rfc7239._host;
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (forwardedHost != null)
{
HostPortHttpField auth = new HostPortHttpField(forwardedHost);
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (_proxyAsAuthority)
{
if (rfc7239!=null && rfc7239._by!=null)
{
HostPortHttpField auth = rfc7239._by;
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (forwardedServer != null)
{
request.setAuthority(forwardedServer,request.getServerPort());
}
}
// handle remote end identifier
if (rfc7239!=null && rfc7239._for!=null)
{
request.setRemoteAddr(InetSocketAddress.createUnresolved(rfc7239._for.getHost(),rfc7239._for.getPort()));
}
else if (forwardedFor != null)
{
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor.getHost(), (forwardedFor.getPort() > 0) ? forwardedFor.getPort() : request.getRemotePort()));
}
// handle protocol identifier
if (rfc7239!=null && rfc7239._proto!=null)
{
request.setScheme(rfc7239._proto);
if (rfc7239._proto.equals(config.getSecureScheme()))
request.setSecure(true);
}
else if (forwardedProto != null)
{
request.setScheme(forwardedProto);
if (forwardedProto.equals(config.getSecureScheme()))
request.setSecure(true);
}
else if (forwardedHttps !=null && ("on".equalsIgnoreCase(forwardedHttps)||"true".equalsIgnoreCase(forwardedHttps)))
{
request.setScheme(HttpScheme.HTTPS.asString());
if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme()))
request.setSecure(true);
}
}
/* ------------------------------------------------------------ */
protected String getLeftMost(String headerValue)
{
if (headerValue == null)
return null;
int commaIndex = headerValue.indexOf(',');
if (commaIndex == -1)
{
// Single value
return headerValue;
}
// The left-most value is the farthest downstream client
return headerValue.substring(0,commaIndex).trim();
}
protected HostPort getRemoteAddr(String headerValue)
{
String leftMost = getLeftMost(headerValue);
if (leftMost == null)
{
return null;
}
try
{
return new HostPort(leftMost);
}
catch (Exception e)
{
// failed to parse in host[:port] format
LOG.ignore(e);
return null;
}
}
@Override
public String toString()
{
return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
}
@Deprecated
public String getHostHeader()
{
return _forcedHost.getValue();
}
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
*
* @param hostHeader
* The value of the host header to force.
*/
@Deprecated
public void setHostHeader(String hostHeader)
{
_forcedHost = new HostPortHttpField(hostHeader);
}
private final class RFC7239 extends QuotedCSV
{
HostPortHttpField _by;
HostPortHttpField _for;
HostPortHttpField _host;
String _proto;
private RFC7239()
{
super(false);
}
@Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
if (valueLength==0 && paramValue>paramName)
{
String name=StringUtil.asciiToLowerCase(buffer.substring(paramName,paramValue-1));
String value=buffer.substring(paramValue);
switch(name)
{
case "by":
if (_by==null && !value.startsWith("_") && !"unknown".equals(value))
_by=new HostPortHttpField(value);
break;
case "for":
if (_for==null && !value.startsWith("_") && !"unknown".equals(value))
_for=new HostPortHttpField(value);
break;
case "host":
if (_host==null)
_host=new HostPortHttpField(value);
break;
case "proto":
if (_proto==null)
_proto=value;
break;
}
}
}
}
}