All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.ForwardedRequestCustomizer Maven / Gradle / Ivy

//
//  ========================================================================
//  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.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
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.QuotedCSVParser;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;

import static java.lang.invoke.MethodType.methodType;

/**
 * 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 HostPortHttpField _forcedHost; private boolean _proxyAsAuthority = false; private boolean _forwardedPortAsAuthority = true; private String _forwardedHeader = HttpHeader.FORWARDED.toString(); private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); private String _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString(); private String _forwardedHttpsHeader = "X-Proxied-Https"; private String _forwardedCipherSuiteHeader = "Proxy-auth-cert"; private String _forwardedSslSessionIdHeader = "Proxy-ssl-id"; private boolean _sslIsSecure = true; private Trie _handles; public ForwardedRequestCustomizer() { updateHandles(); } /** * @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; _forwardedServerHeader = null; _forwardedForHeader = null; _forwardedPortHeader = 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 (_forwardedPortHeader == null) _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString(); if (_forwardedProtoHeader == null) _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); if (_forwardedHttpsHeader == null) _forwardedHttpsHeader = "X-Proxied-Https"; } updateHandles(); } 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(new ForcedHostPort(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) { if (_forwardedHeader == null || !_forwardedHeader.equals(forwardedHeader)) { _forwardedHeader = forwardedHeader; updateHandles(); } } public String getForwardedHostHeader() { return _forwardedHostHeader; } /** * @param forwardedHostHeader The header name for forwarded hosts (default {@code X-Forwarded-Host}) */ public void setForwardedHostHeader(String forwardedHostHeader) { if (_forwardedHostHeader == null || !_forwardedHostHeader.equalsIgnoreCase(forwardedHostHeader)) { _forwardedHostHeader = forwardedHostHeader; updateHandles(); } } /** * @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) { if (_forwardedServerHeader == null || !_forwardedServerHeader.equalsIgnoreCase(forwardedServerHeader)) { _forwardedServerHeader = forwardedServerHeader; updateHandles(); } } /** * @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) { if (_forwardedForHeader == null || !_forwardedForHeader.equalsIgnoreCase(forwardedRemoteAddressHeader)) { _forwardedForHeader = forwardedRemoteAddressHeader; updateHandles(); } } public String getForwardedPortHeader() { return _forwardedPortHeader; } /** * @param forwardedPortHeader The header name for forwarded hosts (default {@code X-Forwarded-Port}) */ public void setForwardedPortHeader(String forwardedPortHeader) { if (_forwardedPortHeader == null || !_forwardedPortHeader.equalsIgnoreCase(forwardedPortHeader)) { _forwardedPortHeader = forwardedPortHeader; updateHandles(); } } /** * @return if true, the X-Forwarded-Port header applies to the authority, * else it applies to the remote client address */ public boolean getForwardedPortAsAuthority() { return _forwardedPortAsAuthority; } /** * Set if the X-Forwarded-Port header will be used for Authority * * @param forwardedPortAsAuthority if true, the X-Forwarded-Port header applies to the authority, * else it applies to the remote client address */ public void setForwardedPortAsAuthority(boolean forwardedPortAsAuthority) { _forwardedPortAsAuthority = forwardedPortAsAuthority; } /** * 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) { if (_forwardedProtoHeader == null || !_forwardedProtoHeader.equalsIgnoreCase(forwardedProtoHeader)) { _forwardedProtoHeader = forwardedProtoHeader; updateHandles(); } } /** * @return The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert}) */ public String getForwardedCipherSuiteHeader() { return _forwardedCipherSuiteHeader; } /** * @param forwardedCipherSuiteHeader The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert}) */ public void setForwardedCipherSuiteHeader(String forwardedCipherSuiteHeader) { if (_forwardedCipherSuiteHeader == null || !_forwardedCipherSuiteHeader.equalsIgnoreCase(forwardedCipherSuiteHeader)) { _forwardedCipherSuiteHeader = forwardedCipherSuiteHeader; updateHandles(); } } /** * @return The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id}) */ public String getForwardedSslSessionIdHeader() { return _forwardedSslSessionIdHeader; } /** * @param forwardedSslSessionIdHeader The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id}) */ public void setForwardedSslSessionIdHeader(String forwardedSslSessionIdHeader) { if (_forwardedSslSessionIdHeader == null || !_forwardedSslSessionIdHeader.equalsIgnoreCase(forwardedSslSessionIdHeader)) { _forwardedSslSessionIdHeader = forwardedSslSessionIdHeader; updateHandles(); } } /** * @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) { if (_forwardedHttpsHeader == null || !_forwardedHttpsHeader.equalsIgnoreCase(forwardedHttpsHeader)) { _forwardedHttpsHeader = forwardedHttpsHeader; updateHandles(); } } /** * @return true if the presence of an 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 an 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(); // Do a single pass through the header fields as it is a more efficient single iteration. Forwarded forwarded = new Forwarded(request, config); try { for (HttpField field : httpFields) { MethodHandle handle = _handles.get(field.getName()); if (handle != null) handle.invoke(forwarded, field); } } catch (Throwable e) { throw new RuntimeException(e); } if (forwarded._proto != null) { request.setScheme(forwarded._proto); if (forwarded._proto.equalsIgnoreCase(config.getSecureScheme())) request.setSecure(true); } if (forwarded._host != null) { httpFields.put(new HostPortHttpField(forwarded._host)); request.setAuthority(forwarded._host.getHost(), forwarded._host.getPort()); } if (forwarded._for != null) { int port = forwarded._for.getPort() > 0 ? forwarded._for.getPort() : request.getRemotePort(); request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._for.getHost(), port)); } } 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(); } @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 void updateHandles() { int size = 0; MethodHandles.Lookup lookup = MethodHandles.lookup(); // Loop to grow capacity of ArrayTrie for all headers while (true) { try { size += 128; // experimented good baseline size _handles = new ArrayTrie<>(size); if (updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite")) continue; if (updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId")) continue; if (updateForwardedHandle(lookup, getForwardedHeader(), "handleRFC7239")) continue; if (updateForwardedHandle(lookup, getForwardedForHeader(), "handleFor")) continue; if (updateForwardedHandle(lookup, getForwardedPortHeader(), "handlePort")) continue; if (updateForwardedHandle(lookup, getForwardedHostHeader(), "handleHost")) continue; if (updateForwardedHandle(lookup, getForwardedProtoHeader(), "handleProto")) continue; if (updateForwardedHandle(lookup, getForwardedHttpsHeader(), "handleHttps")) continue; if (updateForwardedHandle(lookup, getForwardedServerHeader(), "handleServer")) continue; break; } catch (NoSuchMethodException | IllegalAccessException e) { throw new IllegalStateException(e); } } } private boolean updateForwardedHandle(MethodHandles.Lookup lookup, String headerName, String forwardedMethodName) throws NoSuchMethodException, IllegalAccessException { final MethodType type = methodType(void.class, HttpField.class); if (StringUtil.isBlank(headerName)) return false; return !_handles.put(headerName, lookup.findVirtual(Forwarded.class, forwardedMethodName, type)); } private static class ForcedHostPort extends HostPort { ForcedHostPort(String authority) { super(authority); } } private static class PossiblyPartialHostPort extends HostPort { PossiblyPartialHostPort(String authority) { super(authority); } protected PossiblyPartialHostPort(String host, int port) { super(host, port); } } private static class PortSetHostPort extends PossiblyPartialHostPort { PortSetHostPort(String host, int port) { super(host, port); } } private static class Rfc7239HostPort extends HostPort { Rfc7239HostPort(String authority) { super(authority); } } private class Forwarded extends QuotedCSVParser { HttpConfiguration _config; Request _request; boolean _protoRfc7239; String _proto; HostPort _for; HostPort _host; public Forwarded(Request request, HttpConfiguration config) { super(false); _request = request; _config = config; if (_forcedHost != null) _host = _forcedHost.getHostPort(); } @SuppressWarnings("unused") public void handleCipherSuite(HttpField field) { _request.setAttribute("javax.servlet.request.cipher_suite", field.getValue()); if (isSslIsSecure()) { _request.setSecure(true); _request.setScheme(_config.getSecureScheme()); } } @SuppressWarnings("unused") public void handleSslSessionId(HttpField field) { _request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue()); if (isSslIsSecure()) { _request.setSecure(true); _request.setScheme(_config.getSecureScheme()); } } public void handleHost(HttpField field) { if (getForwardedPortAsAuthority() && !StringUtil.isEmpty(getForwardedPortHeader())) { if (_host == null) _host = new PossiblyPartialHostPort(getLeftMost(field.getValue())); else if (_for instanceof PortSetHostPort) _host = new HostPort(HostPort.normalizeHost(getLeftMost(field.getValue())), _host.getPort()); } else if (_host == null) { _host = new HostPort(getLeftMost(field.getValue())); } } @SuppressWarnings("unused") public void handleServer(HttpField field) { if (getProxyAsAuthority()) return; handleHost(field); } @SuppressWarnings("unused") public void handleProto(HttpField field) { if (_proto == null) _proto = getLeftMost(field.getValue()); } @SuppressWarnings("unused") public void handleFor(HttpField field) { if (!getForwardedPortAsAuthority() && !StringUtil.isEmpty(getForwardedPortHeader())) { if (_for == null) _for = new PossiblyPartialHostPort(getLeftMost(field.getValue())); else if (_for instanceof PortSetHostPort) _for = new HostPort(HostPort.normalizeHost(getLeftMost(field.getValue())), _for.getPort()); } else if (_for == null) { _for = new HostPort(getLeftMost(field.getValue())); } } @SuppressWarnings("unused") public void handlePort(HttpField field) { if (!getForwardedPortAsAuthority()) { if (_for == null) _for = new PortSetHostPort(_request.getRemoteHost(), Integer.parseInt(getLeftMost(field.getValue()))); else if (_for instanceof PossiblyPartialHostPort && _for.getPort() <= 0) _for = new HostPort(HostPort.normalizeHost(_for.getHost()), Integer.parseInt(getLeftMost(field.getValue()))); } else { if (_host == null) _host = new PortSetHostPort(_request.getServerName(), Integer.parseInt(getLeftMost(field.getValue()))); else if (_host instanceof PossiblyPartialHostPort && _host.getPort() <= 0) _host = new HostPort(HostPort.normalizeHost(_host.getHost()), Integer.parseInt(getLeftMost(field.getValue()))); } } @SuppressWarnings("unused") public void handleHttps(HttpField field) { if (_proto == null && ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue()))) _proto = HttpScheme.HTTPS.asString(); } @SuppressWarnings("unused") public void handleRFC7239(HttpField field) { addValue(field.getValue()); } @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 (!getProxyAsAuthority()) break; if (value.startsWith("_") || "unknown".equals(value)) break; if (_host == null || !(_host instanceof Rfc7239HostPort)) _host = new Rfc7239HostPort(value); break; case "for": if (value.startsWith("_") || "unknown".equals(value)) break; if (_for == null || !(_for instanceof Rfc7239HostPort)) _for = new Rfc7239HostPort(value); break; case "host": if (value.startsWith("_") || "unknown".equals(value)) break; if (_host == null || !(_host instanceof Rfc7239HostPort)) _host = new Rfc7239HostPort(value); break; case "proto": if (_proto == null || !_protoRfc7239) { _protoRfc7239 = true; _proto = value; } break; } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy