Tomcat and SSL Accelerators

Adam Constabaris


Table of Contents

Tomcat and SSL
What Doesn't Work
So What Does Work?

Tomcat and SSL

Tomcat has its own SSL implementation, which is fine for testing or low-traffic situations. For serious workloads, however, you may want to run Tomcat behind Apache httpd, letting the native-code httpd handle SSL. If you want to take things further, and run Tomcat behind a terminating SSL proxy (an SSL accelerator that handles the SSL encrypted conversation with HTTPS clients and passes unencrypted traffic to Tomcat over HTTP), and you're using a Tomcat version before 6.0, then you're going to need to do a little more work.

In many situations, you want to make sure that certain parts of your Tomcat application are accessed only over HTTPS: is done with the following snippet of the web.xml deployment descriptor:

<security-constraint>
	<web-resource-collection>
		<web-resource-name>Secret Stuff</web-resource-name>
		<url-pattern>/secretstuff</url-pattern>
	</web-resource-collection>
	<user-data-constraint>
		<transport-guarantee>CONFIDENTIAL</transport-guarantee>
	</user-data-constraint>
</security-constraint>

The above snippet means, in practical terms, that you must use SSL to access the parts of the web application mapped under /secretstuff. Tomcat tries to handle this situation seamlessly for the user; if the client requests http://www.someserver.edu/webapp/secretstuff, Tomcat will send back a redirect to https://www.someserver.edu/webapp/secretstuff. In order to work this magic, Tomcat has to be able to detect whether the current channel is secure, and to know how to redirect the request so it will be made again, securely.

This works well with standalone Tomcat, or with Tomcat and Apache connected by AJP; in these situations, Tomcat does a good job of detecting whether the incoming request is secure or not, and knows how to send the redirect to the secure port (usually 443 or 8443). When you're trying to run Tomcat standalone behind a terminating SSL acclerator, things change, however. Such an SSL accelerator works by handling the SSL traffic itself, proxying the request to Tomcat over a non-secure HTTP connection, with a dedicated port. So, the relevant part of your server.xml will look something like this:

 
<Connector port="80"/> 
<!-- this is the
  'secure' port --> 
<Connector port="81"/> 

Note

This is absolutely minimal, in the spirit of trying to start with the least complicated thing that could possibly work. In a real world situation, you'll want to specify some of the attributes for the Connector elements, such as the hostname and the number of threads.

The problem with the SSL Accelerator setup is that, as far as Tomcat is concerned, it's not seeing any secure traffic; the secure part of the conversation takes place between the SSL accelerator and the user's browser. What happens with a reasonably smart browser is that it will attempt to access the protected URL, get a redirect that says "go to https:// whatever", try that URL, and get another redirect to the URL it's trying to go to; it will figure out pretty quickly that this is unsustainable, so it will throw up an error message telling you it's caught in an apparent loop. I shudder to think what would happen if a browser that didn't detect this kind of situation tried to connect.

What's needed is a way to tell Tomcat that connections coming in over the port that's listening to the SSL accelerator's proxied traffic are to be treated as secure.

What Doesn't Work

So What Does Work?

The solution I gravitated toward was using a Tomcat-specific component called a Valve; Valves are much like ServletFilters, only they're Tomcat-specific, can't be configured in the web application's web.xml, and they're invoked earlier in the request processing, before any of the request processors (filters, servlets, etc.) specified in the webapp's deployment descriptor come into the picture. So, the Valve could look something like this:

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

public class SecureProxyValve extends ValveBase {
        
        print int secureProxyPort = 81;
	
	/**
         * Sets the secure proxy port for this <code>Valve</code>
	 * @param secureProxyPort the <em>local</em> over
	 * which incoming requests will be considered secure.
         **/
	public void setSecureProxyPort(int secureProxyPort) {
	    this.secureProxyPort = secureProxyPort;
	}

	/** 
	 * Gets the secure proxy port.
         * @return the local port over which incoming requests are considered
	 * secure.
	 **/
	public int getSecureProxyPort() {
	    return secureProxyPort;
	}
	


	@Override
	public void invoke(Request req, Response resp) throws IOException, ServletException {
		if ( req.getLocalPort() == secureProxyPort ) { 
			req.setSecure(true);
		}

		if ( getNext() != null ) {
			getNext().invoke(req, resp);
		}
	}
}

In a nutshell, it looks at the request, and if it's coming in over the secure port (note the use of getLocalPort, that's very important and it's a new part of the 2.4 servlet spec), and if so sets it to be secure.

To enable the Valve, you need to compile it, put the classfile under $TOMCAT_HOME/server/classes (or, if you package it in a JAR, put the .jar file into $TOMCAT_HOME/server/lib; to enable it, you'll need to add something like the following to your server.xml, for the Context, Host, or Engine you need to "SSL-enable" in this way.

 
  <!-- this will apply the valve to all requests made to this tomcat instance;
       if you need finer grained control, put the Valve in as a child of the
       relevant Host or Context, as appropriate -->
   <Engine name="Catalina" defaultHost="myhost.unc.edu">
     <Valve className="its.webapp.filters.SecureProxyValve" secureProxyPort="81"/>
     <!-- Context and Host definitions below -->
    </Engine>
 

For more specific information about what's involved in configuring Tomcat Valves, see the Tomcat 5.5 Valve Documentation.