Tomcat manager with SSO and a different Realm
Tomcat Manager's default configuration isn't pratical (nor secure): you must store your users and passwords in a plain XML file. Of course, you can change the "<Realm>" definition on "<Engine name='Catalina'>", but it has no effect if you need a host with Single Sign On.
Since Manager needs to share the same host with managed applications, you need to create a context.xml for your Manager:
<?xml version="1.0" encoding="UTF-8"?> <Context path="/manager" privileged="true"> <Realm className="my.secure.and.custom.Realm"/> </Context>
Put it on "conf/<engine>/<host>/manager.xml". The "privileged='true'" will prevent a "java.lang.SecurityException: Servlet of class org.apache.catalina.manager.HTMLManagerServlet is privileged and cannot be loaded by this web application".
You must change "webapps/manager/WEB-INF/web.xml" and replace "<role-name>manager</role-name>" with the name of the role you want.
JAAS + Struts + Tomcat = Secure and pratical
Still working on the application I describe in my previous post, I started to integrate it to use JAAS. It is a Struts-based system running in a Tomcat server. The login was an Struts Action, and a RequestProcessor took care of redirecting to login page.
It was pretty easy the conversion to JAAS:
- I developed a new Realm for Tomcat. It was just a matter of extend the RealmBase class and override the authenticate method. This step could be skipped if my employer's LDAP server accepted anonymous search - JNDIRealm can do this job;
- With the Realm ready, add it to the application's context.xml (NetBeans users will find it inside META-INF folder of the project);
- Next step was to make a "public access" version of the application. Every code that does the authentication and authorization was removed, except the dynamic menu;
- The dynamic menu was changed to use the request.isUserInRole method. For some reason, this property can't be accessed by EL. I expected something like "request.userInRole['role']", but it didn't work;
- The logged user can be retrieved with either request.getUserPrincipal() or the EL "pageContext.request.userPrincipal";
- Now, it is just a matter of adding every Struts action to security constraints in web.xml. In NetBeans, it's an easy-to-use visual editor. Here is a snippet:
<security-constraint> <web-resource-collection> <web-resource-name>SecurePages</web-resource-name> <description>My secret pages</description> <url-pattern>/secure.do</url-pattern> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <description>Allowed roles</description> <role-name>power_user</role-name> </auth-constraint> </security-constraint>
- The login page, if using FORM-based authentication, is now something like:
<form action="j_security_check" method="post"> User: <input type="text" name="j_username"/> Password: <input type="password" name="j_password"/> <input type="submit" name="btLogin" value="login"/> </form>
- To activate the FORM-based login, this must be added to web.xml:
<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/login.jsp</form-error-page> </form-login-config> </login-config>
- Did you notice the login error page is the same as the login page? To check if the user entered an invalid password, I use:
<c:if test="${param.btLogin == 'login' and param.j_username <> ''}"> <c:out value="Invalid username and/or password"/> </c:if>
It is a long boilerplate, but steps are the same for every application. Using JAAS instead of programatic authorization makes your system more secure and easier to maintain.