Tomcat, Atlassian Crowd and JAAS
I have recently rolled out a single sign-on solution using Atlassian Crowd. My client's applications were running on Apache Tomcat and required FORM based authentication. After briefly Googling around, I have found the Crowd JAAS Login Module (see https://plugins.atlassian.com/plugin/details/6045) which fitted my requirements perflectly.
Here is an example of how to integrate a Crowd JAAS login module into Tomcat so that all web applications can be authenticated against the Crowd server.
The following instructions were tested against Crowd 2.0 and Tomcat 6.0.29. You will need at least Tomcat 6.0.20 to make some of the instructions that follow work.
Let's begin.
First step is to prepare the Tomcat server to authenticate users against Crowd. To ensure that you can still log into the Tomcat manager application, wrap the UserDatabase realm in a CombinedRealm element (note the CombinedRealm class is only available in Tomcat 6.0.20 and above). Add the Crowd realm definition inside the CombinedRealm as well.
Note: You don't have to do this if you intend to add your Tomcat administrator user and roles to Crowd. If you do, then you can repalce the UserDatabase realm with your Crowd realm.
If you need single sign-on, make sure to uncomment the SingleSignOn valve inside the Host element.
server.xml
|
<Realm className="org.apache.catalina.realm.CombinedRealm">
<Realm resourceName="UserDatabase"
className="org.apache.catalina.realm.UserDatabaseRealm" />
<Realm appName="Crowd"
className="org.apache.catalina.realm.JAASRealm" userClassNames="com.atlassian.crowd.application.jaas.CrowdPrincipal" roleClassNames="com.atlassian.crowd.application.jaas.CrowdPrincipal" />
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
</Host>
|
I don't like to mix Tomcat core libraries with third party libraries. So, instead of adding the Crowd login module library (and its dependencies) to the Tomcat's lib directory, create a new directory, lib-crowd and add a reference to it in the catalina.properties file. For example:
catalina.properties
|
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar, \
${catalina.home}/lib,${catalina.home}/lib/*.jar, \
${catalina.home}/lib-crowd,${catalina.home}/lib-crowd/*.jar
|
Which dependencies do you need? Truthfully, I haven't properly worked that out yet. Besides the CrowdJaasLoginModule-{version}.jar and crowd-integration-client-{version}.jar, I basically added all the jars that I found in the Crowd client/lib directory.
Finally, we need to configure the Crowd JAAS module. Add a crowd.conf file to Tomcat's conf directory. Here is a basic JAAS module configuration to get you started (see the Crowd JAAS Login Module wiki for a full list of all the options that you can use).
crowd.conf
|
Crowd {
com.atlassian.crowd.application.jaas.CrowdLoginModule required
application.name=app-name application.password=app-password
crowd.server.url="http:/host:port/crowd/services/";
};
|
Edit:
As was pointed out in the comment below, the Tomcat JVM must be told which JAAS login configuration file should be used. The best way is to add the following entry to the CATALINA_OPTS environment variable that is usually initialised in the startup.[sh|bat] or setenv.[sh|bat] scripts:
| -Djava.security.auth.login.config=$CATALINA_HOME/conf/crowd.conf |
And that's it. Well, not quite. I did have some issues with getting Tomcat to recognise the user roles that were added to the Subject by the JAAS module. I posted a solution to this problem to this forum: http://forums.atlassian.com/thread.jspa?messageID=257358007.

Comments
I followed these directions and things worked well except for a couple bumps:
First, I had to add
export CATALINA_OPTS=" -Djava.security.auth.login.config=$CATALINA_HOME/conf/crowd.conf "
to my tomcat startup script so tomcat could find the JAAS config file.
Second, the CombinedRealm did not work for me. In RealmBase.hasRole, it looks like if the realm that authenticated the user (in this case, the nested realm) is not the same as the realm that is being checked for roles (in this case, the combined realm) the user will not be authorized. Note that I did not use the single sign on option but this did not seem to change behavior.
With a couple tweaks, I am up and running. Thanks.
10 November 2010
2 days 16 hours
Just in regards to your second comment, I did have issues when using the CombinedRealm with the SingleSignOn valve. It leads to horrible confusion when accessing applications that require roles from a specific realm.
For example, lets say a user correctly authenticates to realm 1 when accessing application 1 and then navigates to application 2 that requires authentication against realm 2. Since we are using the SingleSignOn valve the user won't be asked to re-authenticate. Instead, the credentials from realm 1 stored in a cookie will be used. This will raise an access denied error as the required role will be missing (since it only exists in realm 2)!
For this scenario to work, we need the system to recognise that the realm the user is currently authenticated against is different to the one required but the application (as per its web.xml) and prompt the user to re-login. Kind of defeats the purpose of single sign-on but that's what you get for using multiple realms!
10 November 2010
2 days 16 hours
Thanks for your feedback.
I am bit surprised that a CombinedRealm did not work for you. I wonder if perhaps our versions of Tomcat are different (I am using 6.0.29).
I had a look at the RealmBase.hasRole method and it does check whether the current realm (i.e. CombinedRealm) is the same as the authenticating realm but besides logging that it is not, it continues without a problem.
Another tweak I had to make was to use different classes for the user and role. This way tomcat can distinguish between the user and role principles and the request.getUserPrinciple() works.
I did this by changing the server.xml:
<Realm appName="crowd" className="org.apache.catalina.realm.JAASRealm" userClassNames="com.atlassian.crowd.application.jaas.CrowdPrincipal" roleClassNames="com.atlassian.crowd.application.jaas.CrowdGroup" />
and crowd.conf:
roleClassName="com.atlassian.crowd.application.jaas.CrowdGroup"
10 November 2010
2 days 16 hours
Thanks again.
I did not test request.getUserPrincipal() method but clearly it is pretty important (fundamental really). This definitely will save me (and perhaps others as well) hours of debugging!
10 November 2010
2 days 16 hours
As of 1st of December the problem with Tomcat not recognising the user roles has been fixed! Just use flatRoles=true option to tell the module not to group user roles within the CrowdGroup object. For more information read this:
https://studio.plugins.atlassian.com/wiki/display/JAAS/Crowd+JAAS+Login+...
Post new comment