Table of Contents
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"/>
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.
Tomcat 5.5: setting the secure attribute of the
Tomcat Connector in server.xml to
true; this tells Tomcat to set up its own
SSL services, which is the business we don't want to be in. [ Update
: Setting the secure
attribute should work without attempting to enable Tomcat's SSL framework as of
Tomcat 6.0; at least, that's my reading. ]
The solution described at http://marc.theaimsgroup.com/?l=tomcat-user&m=105070277803721&w=2,
which is to set the scheme on your
"secure" connector to https and
proxyPort to 443.
Note that the poster in that thread is speaking specifically
about the CoyoteConnector and not the
HTTP Connector at use in Tomcat 5.x.;
this appears to be something that changed between Tomcat 4.1
and Tomcat 5, as noted in this
post on the tomcat-dev mailing list.
Writing a ServletFilter to intercept
the request, determine whether it's on the 'secure' report,
and wrap it in an
HttpServletRequestWrapper that's had
its secure field set to
true; with Tomcat, filters are invoked
after the phase that handles the
redirection, so they'll never be seen.
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.