AppFuse provides a sweet starting point for developing web applications. You choose the frameworks, AppFuse generates the skeleton application.
At its core, the web security of AppFuse 2.0.1 and earlier applications relies on the modular and extensible Acegi authentication framework. In this tutorial, we look at a basic integration of Crowd with Acegi, using an application generated by AppFuse.
Step 1. Get AppFuse
In this tutorial, we will be using the Struts2-basic archetype to create the project, but the other types should be similar. For more information, consult the AppFuse quickstart guide. In particular, it outlines the database requirements for AppFuse.
- Create the project.
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes \ -DarchetypeArtifactId=appfuse-basic-struts \ -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0 \ -DgroupId=com.mycompany.app -DartifactId=myproject
- Since we will be editing the core Acegi configuration, we will need the full source code of the application.
cd myproject mvn appfuse:full-source
- Build it.
mvn clean install
- Run it.
mvn jetty:run-war -Dmaven.test.skip
- Play with it.
http://localhost:8080/
- Shut it down.
ctrl+c
Step 2. Let Crowd Know about AppFuse
Add appfuse
as an application via the Crowd Console. See Adding an Application for more information.
Step 3. Add the Crowd Acegi Connector to AppFuse
Open up the pom.xml
and add the Crowd client libraries as a project dependency:
<dependencies> <dependency> <groupId>com.atlassian.crowd</groupId> <artifactId>crowd-integration-client</artifactId> <version>1.5.1</version> </dependency> ... </dependencies>
You will also need to create the file myproject/src/main/resources/crowd.properties
:
application.name appfuse application.password password application.login.url http://localhost:8095/crowd/ crowd.server.url http://localhost:8095/crowd/services/ session.isauthenticated session.isauthenticated session.tokenkey session.tokenkey session.validationinterval 0 session.lastvalidation session.lastvalidation
In particular, the application name and password must match the values defined for the application added in Step 2.
Finally, copy the STANDALONE/client/conf/crowd-ehcache.xml
to myproject/src/main/resources/crowd-ehcache.xml
. This file defines the cache properties, such as cache timeouts, used when accessing data from the Crowd server.
Step 4. Hook Up Centralised Authentication
Before modifying the security configuration, you will need to add the Spring configuration file to wire up the Crowd client beans. Add the applicationContext-CrowdClient.xml
configuration file to the list of contextConfigLocations
in WEB-INF/web.xml
:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:/applicationContext-resources.xml classpath:/applicationContext-dao.xml classpath:/applicationContext-service.xml classpath*:/applicationContext.xml classpath:/applicationContext-CrowdClient.xml /WEB-INF/applicationContext*.xml /WEB-INF/xfire-servlet.xml /WEB-INF/security.xml </param-value> </context-param>
AppFuse neatly stores all the Acegi configuration in myproject/src/main/webapp/WEB-INF/security.xml
. In order to get centralised authentication, we will need to set up Acegi to use the wrapped authenticator class we just created. Edit the Acegi beans in security.xml
:
- Add the definition of the CrowdUserDetailsService:
<bean id="crowdUserDetailsService" class="com.atlassian.crowd.integration.acegi.user.CrowdUserDetailsServiceImpl"> <property name="authenticationManager" ref="crowdAuthenticationManager"/> <property name="groupMembershipManager" ref="crowdGroupMembershipManager"/> <property name="userManager" ref="crowdUserManager"/> <property name="authorityPrefix" value="ROLE_"/> </bean>
- Add the definition of the RemoteCrowdAuthenticationProvider which will delegate Acegi's authentication requests to Crowd:
<bean id="crowdAuthenticationProvider" class="com.atlassian.crowd.integration.acegi.RemoteCrowdAuthenticationProvider"> <constructor-arg ref="crowdAuthenticationManager"/> <constructor-arg ref="httpAuthenticator"/> <constructor-arg ref="crowdUserDetailsService"/> </bean>
- Replace the DaoAuthenticationProvider with our authenticator in the authentication manager:
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref local="crowdAuthenticationProvider"/> <!--ref local="daoAuthenticationProvider"--> <ref local="anonymousAuthenticationProvider"/> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean>
- Now do a:
mvn jetty:run-war -Dmaven.test.skip
- Head over to
http://localhost:8080/
.
You should now be able to authenticate the users in your Crowd repository that meet all of the following conditions:
- They are in a Crowd directory assigned to the AppFuse application in Crowd. See more information.
- They are in Crowd groups named
USER
andADMIN
. You will need to add these groups and assign the user as a member of the groups. These Crowd group names map to the Acegi authorisation roles defined in the AppFuse application. - They are allowed to authenticate with the AppFuse application because EITHER they are in a group allowed to authenticate with Crowd see more OR their container directory allows all users to authenticate see more.
Congratulations. You have centralised authentication
Step 5. Hook Up Single Sign-On
Enabling single sign-on (SSO) requires a little more tweaking of the security.xml
:
- Change the default processing filter to Crowd's SSO filter:
<bean id="authenticationProcessingFilter" class="com.atlassian.crowd.integration.acegi.CrowdSSOAuthenticationProcessingFilter"> <property name="httpAuthenticator" ref="httpAuthenticator"/> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationFailureUrl" value="/login.jsp?error=true"/> <property name="defaultTargetUrl" value="/"/> <property name="filterProcessesUrl" value="/j_security_check"/> <property name="rememberMeServices" ref="rememberMeServices"/> </bean>
- Add the definition of the CrowdLogoutHandler:
<bean id="crowdLogoutHandler" class="com.atlassian.crowd.integration.acegi.CrowdLogoutHandler"> <property name="httpAuthenticator" ref="httpAuthenticator"/> </bean>
- Update the definition of the LogoutFilter to use the CrowdLogoutHandler. You may need to uncomment the logout filter.
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter"> <constructor-arg value="/index.jsp"/> <constructor-arg> <list> <ref bean="rememberMeServices"/> <ref bean="crowdLogoutHandler"/> <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/> </list> </constructor-arg> <property name="filterProcessesUrl" value="/logout.jsp"/> </bean>
- If the logout filter is not defined in the filter invocation list, you will need to add it:
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> ... /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> ...
- Now repeat:
mvn jetty:run-war -Dmaven.test.skip=true
SSO will only work for users that are able to authenticate with both appplications and are authorised to use both applications. Try out the following:
- Log in to Crowd – you should be logged in to AppFuse.
- Log out of AppFuse – you should be logged out of Crowd.
- Log in to AppFuse; log out of Crowd; log in to Crowd as another user; refresh AppFuse – you should be logged in as the new user.
Congratulations, you have SSO