One of the cool things about working for Internal Systems is the opportunity to work on new systems. My most recent project is the development of my ShipIt VII project into a fully-fledged application.
Where my ShipIt VII project was a standard Java web application, I’m doing the real version using Grails with Acegi. However, I have to use our existing Crowd instance, to leverage its user base and its single sign-on capabilities. While there’s some good documentation out there about the individual components, it’s still a little bit of work to get Grails + Acegi + Crowd going together. This is how I did it.

Step 1: Grails & Acegi Installation

I downloaded and installed Grails v1.0.1 and Grails’ Acegi Security Plugin v0.2, and set my

1
GRAILS_HOME

and

1
PATH

environment variables in

1
.bash_login

so I didn’t drive myself batshit insane by having to set them for each new terminal window.

Step 2: Create Grails Application with Acegi Security

I created my application via the command line,

Kate:grails kellingburg$ grails create-app AcegiApp

installed the Acegi Plugin,

Kate:grails kellingburg$ cd AcegiApp/
Kate:AcegiApp kellingburg$ grails install-plugin PATH_TO_PLUGIN/grails-acegi-0.2.zip

created the domain classes that Acegi uses,

Kate:AcegiApp kellingburg$ grails create-auth-domains

and created a controller that only authorised users are able to access.

Kate:AcegiApp kellingburg$ grails create-controller AuthOnly

I then edited

1
/conf/Bootstrap.groovy

to add the Requestmap object that will secure the controller (note that the url is all in lowercase),

class BootStrap {
def init = { servletContext ->
new Requestmap(url:"/authonly/**",configAttribute:"ROLE_USER").save()
}
def destroy = {
}
}

put some dummy code in

1
/controllers/AuthOnlyController.groovy

,

class AuthOnlyController {
def index = {
render "Hello, World!"
}
}

and finally edited

1
/web-app/index.gsp

to use some of the Acegi taglibs:

<h3>
<g:isLoggedIn>Hello <b><g:loggedInUserInfo field="username"/></b>!</g:isLoggedIn>
<g:isNotLoggedIn>You are not logged in</g:isNotLoggedIn>
</h3>
<g:ifAllGranted role="ROLE_ADMIN,ROLE_TEST">-- ROLE_ADMIN and ROLE_TEST granted<br/></g:ifAllGranted>
<g:ifAnyGranted role="ROLE_ADMIN,ROLE_TEST">-- ROLE_ADMIN or ROLE_TEST granted<br/></g:ifAnyGranted>
<g:ifNotGranted role="ROLE_TEST">-- ROLE_TEST not granted<br/></g:ifNotGranted>
<g:ifAnyGranted role="ROLE_USER">-- ROLE_USER granted<br/></g:ifAnyGranted>

I was now ready to run the application!

Kate:AcegiApp kellingburg$ grails run-app

Browsing to

1
http://localhost:8080/AcegiApp/

showed the front page, and as expected, I was not logged in:
grails1.jpg
Clicking on AuthOnlyController brought up the login screen:
grails2.jpg
If this was a Grails + Acegi application, I could now begin to add users and roles to my application (by default, Acegi calls these Person objects and Authority objects). But I’m going to get my users from Crowd instead.

Step 3: Crowd Download

I grabbed a beta version of Crowd v1.3, but seeing as it was released earlier this week, you can download Crowd v1.3 from the Atlassian website.

Step 4: Crowd Installation & Configuration

I followed the Crowd v1.3 installation instructions to install Crowd locally. When my application is deployed, I will integrate with Atlassian’s Crowd instance, but for development I wanted to set up my own. Because I wanted set up to be as painless as possible, I chose to use the database supplied with Crowd. I was able to quickly grab an evaluation license from the My Account section of the Atlassian website.
Installing and configuring Crowd was a cinch. My previous experiences with single-sign on products had left me very hesitant to get my feet wet with Crowd, but boy am I glad I did. It’s hard to imagine how it could have been easier.
I configured one directory, one application, two users and two groups. The group names matched the Authorities that my AcegiApp will expect: ROLE_USER and ROLE_ADMIN.
To check that I’d set everything up correctly, Crowd provides an option under “Config Test” that verifies whether a user can authenticate with the application:
crowd1.jpg

Step 5: Grails + Acegi + Crowd Integration

Crowd comes with centralised authentication and single sign-on connectors for Acegi, and some pretty darn good instructions. However, because this was a Grails app, I had to do things slightly differently.

crowd.properties

I copied

1
crowd.properties

from

1
CROWD_INSTALL/client/conf

to

1
ACEGI_APP

. It would be nice if Grails provided a

1
/properties

folder to hold properties files, but I needed to put the file in the root directory. I needed to edit this file as per the Crowd documentation:

application.name acegiapp
application.password password
application.login.url http://localhost:8080/AcegiApp/login/auth

applicationContext-CrowdClient.xml

I copied

1
applicationContext-CrowdClient.xml

from the

1
CROWD/client/crowd-integration-client-1.3.jar

to

1
ACEGI_APP/conf/spring

and renamed it

1
resources.xml

.
I added some beans to

1
resources.xml

, similar to but not the same as the Crowd documentation:

<bean id="crowdUserDetailsService" class="com.atlassian.crowd.integration.acegi.CrowdUserDetailsService">
<property name="securityServerClient" ref="securityServerClient"/>
</bean>
<bean id="crowdAuthenticationProvider" class="com.atlassian.crowd.integration.acegi.CrowdAuthenticationProvider">
<property name="userDetailsService" ref="crowdUserDetailsService"/>
<property name="httpAuthenticator" ref="httpAuthenticator"/>
<property name="securityServerClient" ref="securityServerClient"/>
</bean>
<bean id="authenticationProcessingFilter" class="com.atlassian.crowd.integration.acegi.CrowdAuthenticationProcessingFilter">
<property name="httpAuthenticator" ref="httpAuthenticator"/>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login/authfail?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
</bean>
<bean id="crowdLogoutHandler" class="com.atlassian.crowd.integration.acegi.CrowdLogoutHandler">
<property name="httpAuthenticator" ref="httpAuthenticator"/>
</bean>
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/"/>
<constructor-arg>
<list>
<ref bean="crowdLogoutHandler"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_acegi_logout"/>
</bean>

jar files

I copied the following jar files to the

1
/lib

directory. This step is where I really, really missed Maven 2 — I spent a lot of time tracking down dependencies, particularly of jars not provided in

1
CROWD/client/lib

. (Note: this was a problem in the beta version of Crowd 1.3, the Crowd developers assure me they’ve since fixed this.)

activation-1.1.jar
atlassian-core-3.8.jar
commons-codec-1.3.jar
commons-collections-3.2.jar
commons-httpclient-3.0.jar
crowd-integration-client-1.3.jar
jaxen-1.1-beta-9.jar
jdom-1.0.jar
propertyset-1.3-21Nov03.jar
stax-api-1.0.1.jar
wsdl4j-1.6.1.jar
wstx-asl-3.2.4.jar
xfire-aegis-1.2.6.jar
xfire-core-1.2.6.jar
xwork-1.2.3.jar

I didn’t copy all the files in

1
CROWD/client/lib

as Grails includes many of the dependencies by default.

AcegiGrailsPlugin.groovy

I then edited

1
ACEGI_APP/plugins/acegi-0.2/AcegiGrailsPlugin.groovy

— which means I’ll have to be careful if I ever upgrade the plugin.
At line 91, I removed the declarations of

1
logoutFilter

and

1
authenticationProcessingFilter

, and at line 179, I replaced

1
daoAuthenticationProvider

with

1
crowdAuthenticationProvider

:

authenticationManager(org.acegisecurity.providers.ProviderManager){
providers=[
ref("crowdAuthenticationProvider"),
ref("anonymousAuthenticationProvider"),
ref("rememberMeAuthenticationProvider")]
}

AuthorizeTagLib.groovy

I changed line 70 in

1
ACEGI_APP/plugins/acegi-0.2/grails-app/taglib/AuthorizeTagLib.groovy

so that it didn’t refer to the

1
domainClass

field. This is because the

1
authPrincipal

is now a

1
com.atlassian.crowd.integration.acegi.CrowdUserDetails

object, and it does not have a

1
domainClass

field.

def loggedInUserInfo = {attrs,body->
def authPrincipal = SCH?.context?.authentication?.principal
if( authPrincipal!=null && authPrincipal!="anonymousUser"){
//out << authPrincipal?.domainClass?."${attrs.field}"
out << authPrincipal?."${attrs.field}"
}else{
out << body()
}
}

As with

1
AcegiGrailsPlugin.groovy

, I’ll have to be careful if I ever upgrade the plugin.

Step 5: Grails + Acegi + Crowd

I started up my Grails applications, AcegiApp, went to the front page, clicked on AuthOnly and logged in as the first user I created. I’m now able to see the page:
grails3.jpg
And my front page now shows some information about the logged in user:
grails4.jpg

In Summary

Getting started with the individual components was pretty straight forward. I did a lot of trial-and-error to figure out how to integrate with Crowd, but the Crowd documentation was a great starting place. Some of that trial-and-error was due to *ahem* developer issues rather than Grails, Acegi or Crowd.
Two areas of uncoolness: transitive dependencies and plugin editing.
I really, really missed having Maven 2 support for Grails (currently the maven-grails-plugin does not support Grails v1.0.1) purely because of transitive dependency resolution.
I had to edit the two of the plugin files, which means I will have to be careful when upgrading the plugin. Plus, I ignore some of the fields in

1
AcegiConfig.groovy

. Ideally, integrating Crowd would require changes only to

1
AcegiConfig.groovy

and any fields set here would be referenced by the Crowd beans.
The biggest surprise from the whole endeavour – besides spending hours on an issue that arose because I typed a comma instead of a full stop – was how easy Crowd was. Installation and configuration were a breeze, and quick to boot. All Internal Systems applications will now include Crowd support from the get go – not because we have to, but because we want to.
Does this sound like fun to you? We are hiring in Internal Systems!