Sunday, April 15, 2012

Jetty 6 HTTPS Redirects

Another Jetty 6 trick I have had to use several times is patching it to enable redirects with the correct URL scheme when proxyied via HTTP behind a webserver using SSL. Because the connection between the webserver and jetty is just plain HTTP (without SSL), jetty will send the redirect with a plain http scheme:

Location: http://example.com/foo

But when the webserver is using SSL, what I really want is for it to send the redirect with an https scheme:

Location: https://example.com/foo

X-Forwarded- Headers

As described in jetty's reverse proxy docs, by setting the forwarded property in jetty's connector configuration, you can get jetty to use the server name/port and remote client IP-address from the X-Forwarded-Host and X-Forwarded-For headers that apache's mod_proxy includes automatically.

One header that mod_proxy does not include automatically, however, is X-Forwarded-Proto. You have to add that manually, via the RequestHeader directive in your apache config (wherever you included the ProxyPass directive that forwards requests to jetty):

ProxyPass http://localhost:8080/ ProxyPassReverse http://localhost:8080/ RequestHeader set X-Forwarded-Proto "https"

Patching Jetty 6

For jetty 7/8, that would be sufficient. With the above configuration, they'll send the correct redirects. But jetty 6 doesn't use the X-Forwarded-Proto header, so you have to create your own connector class to handle it. This is what I've done (for the nio connectors):

package com.pitchstone.lib.jetty; import java.io.IOException; import org.mortbay.io.EndPoint; import org.mortbay.jetty.HttpFields; import org.mortbay.jetty.Request; import org.mortbay.jetty.nio.SelectChannelConnector; /** * Jetty nio connector. * Adds the ability to use the 'X-Forwarded-Proto' header * to set the request 'scheme' property. */ public class NioConnector extends SelectChannelConnector { private String _forwardedProtoHeader = "X-Forwarded-Proto"; public NioConnector() { super(); } // AbstractConnector protected void checkForwardedHeaders(EndPoint endpoint, Request request) throws IOException { super.checkForwardedHeaders(endpoint, request); HttpFields httpFields = request.getConnection().getRequestFields(); String forwardedProto = httpFields.getStringField(getForwardedProtoHeader()); forwardedProto = getLeftMostValue(forwardedProto); if ("http".equals(forwardedProto) || "https".equals(forwardedProto)) request.setScheme(forwardedProto); } // impl public String getForwardedProtoHeader() { return _forwardedProtoHeader; } public void setForwardedProtoHeader(String x) { _forwardedProtoHeader = x; } }

To use it, compile it, jar it up, and add the jar to jetty's lib/ext directory (/usr/share/jetty/lib/ext by default under ubuntu/debian). Then configure jetty.xml to use it, replacing org.mortbay.jetty.nio.SelectChannelConnector with this custom version:

<Call name="addConnector"> <Arg> <New class="org.mortbay.jetty.nio.SelectChannelConnector"> <New class="com.pitchstone.lib.jetty.NioConnector"> <Set name="host"><SystemProperty name="jetty.host" /></Set> ... <Set name="forwarded">true</Set> </New> </Arg> </Call>

With that configuration and patch in place, jetty will now send redirects with the https scheme. It also will map these X-Forwarded-For headers to the ServletRequest API like so:

HeaderServletRequest Method
X-Forwarded-HostgetServerName()
X-Forwarded-HostgetServerPort()
X-Forwarded-ForgetRemoteAddr()
X-Forwarded-ForgetRemoteHost()
X-Forwarded-ProtogetScheme()

2 comments:

  1. I also encountered this problem and solved it using a custom filter that looked at X-Forwarded-Proto. Worked like a charm.

    ReplyDelete
  2. Thanks, the solution worked as a charm.
    -Alexey

    ReplyDelete