I have a Tomcat 6 server containing three webapps: a custom one as ROOT, Jenkins and Nexus.
I would like to secure all three centrally (server.xml?) using BASIC authentication.
How can I achieve this without modifying or configuring the webapps themselves?
First I tried (without success) to include the BasicAuthenticator valve in conf/context.xml. This didn't seem to have any effect.
Finally I got it to work (secured all webapps) by adding this snippet to conf/web.xml :
<security-constraint>
<web-resource-collection>
<web-resource-name>Basic Authentication</web-resource-name>
<!--Here wildcard entry defines authentication is needed for whole app -->
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>myrole</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
<security-role>
<description>My role</description>
<role-name>myrole</role-name>
</security-role>
It can be done, but you'll need to work on it.
For Tomcat, basically what you want is a Tomcat Valve. It's like a Servlet Filter, but it's Tomcat specific.
You can place a Valve in the HOST entry of your server config, then all of the apps within that Host will need to go through that Valve. And that Valve is what you need to handle your BASIC authentication.
Tomcat DOES have a BASIC Authentication Valve already, but it's designed to work with the web app. You can probably grab the source to this and hack on it to work at the Host level rather than at the Web App level, thus protecting all of your apps without configuring them individually.
Now, if you were a bit more open minded, I would suggest the Tomcat Single Sign On, and then augmenting each of the web apps to use BASIC in their web.xml following the Servlet spec. It's a basically trivial change to the individual applications web.xml, but it also solves the problem for you. But you said you didn't want to modify the web apps, so you're stuck using Tomcat specific stuff, and "hand crafting" it yourself at that.
This is possible, but AFAIK it's not possible without (some) code. Here's a solution that does not touch the deployed webapps in any way, but which also does not give you any fine grained authorization, only authentication.
Tomcat 7 (and 6?) have a nifty feature to perform authentication even though there are no protected resources in the web app, called preemtiveAuthentication:
<Context preemptiveAuthentication="true">
<Valve className="org.apache.catalina.authenticator.BasicAuthenticator" />
</Context>
Pop that in your context wherever it may be (perhaps you need to create $CATALINA_BASE/conf/Catalina/localhost/mywebapp.xml
to protect mywebapp.war).
This will make any incoming request with anything in the Authorization header to trigger authorization. Any request without the Authorization header will still get through.
http://example.com:8080/mywebapp/
will work, while GET /
http://user:password@example.com:8080/mywebapp/
will not (or, it will check for a username and password)
The remaining trick is therefore to trip that "feature" every time, even for users that don't send any authorization header. This is where I had to revert to a Valve.
Here is the code for a valve that sets a request header "Authorization" to "foo" if it isn't present.
import javax.servlet.ServletException;
import java.io.IOException;
import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class ConditionallyAddFakeAuthorizationHeader extends ValveBase {
public void invoke(Request request, Response response) throws IOException, ServletException {
if (request.getCoyoteRequest().getMimeHeaders().getValue("authorization") == null) {
request.getCoyoteRequest().getMimeHeaders().addValue("authorization").setString("foo");
}
getNext().invoke(request, response);
}
}
Compile the file, give it a nice package if you want and put it in Tomcat's shared classpath, and add change your mywebapp.xml as follows (add the new valve before the basic authenicator!):
<Context preemptiveAuthentication="true">
<Valve className="ConditionallyAddFakeAuthorizationHeader"/>
<Valve className="org.apache.catalina.authenticator.BasicAuthenticator" />
</Context>
And voilá, your context will not allow any request through, unless it is authenticated towards the realm you have defined.