This is bulk of the effort here, but it's mostly just XML file editing and small configuration differences to fit in your environment.
Step A: Add Acegi configurations to your web.xml
Add this filter and then map it to your application. Note that many examples you may see will have the targetClass defined to be AuthenticationProcessingFilter...don't use that one; it's just a subset of the larger filter chain we'll set up in our application context.
Code:
<!-- Note: this replaces your original Tapestry filter and filter-mapping -->
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Still in the web.xml, add an additional file (in my case, application-context-acegi.xml) to your contextConfigLocation that we'll use to store the Acegi configuration information. I also found it necessary to intercept the 403 error code to provide seamless integration with my application when someone hits and a page they're not authorized to see.
Code:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:edis3-ws-client-context.xml, classpath:application-context-acegi.xml</param-value>
</context-param>
<error-page>
<error-code>403</error-code>
<location>/AccessDenied.html</location>
</error-page>
Step B: Create the application-context-acegi.xml file
I created a new file in the WEB-INF/classes location of my project and added the following configuration settings, which I've commented in the code:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- ======================== FILTER CHAIN ======================= -->
<!-- Note: I got rid of the 'rememberMe' and 'switchUser' filters you see in a lot of examples. They were confusing the debugging, and now that I understand what's going on, they'll be easy to add in later if I need them -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
<!-- ======================== AUTHENTICATION ======================= -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
</list>
</property>
</bean>
<!-- NOTE NOTE NOTE NOTE BE CAREFUL HERE
The inMemoryDaoImpl DOES NOT support the passwordEncoder -->
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
tom=tvaughan,ROLE_USER,ROLE_SYSTEM_ADMIN
sue=stillery,ROLE_USER,ROLE_SYSTEM_ADMIN
carlos=cfernandez,ROLE_USER,ROLE_USER_ADMIN
joel=jmoeller,ROLE_USER,ROLE_USER_ADMIN
tony=tgiaccone,ROLE_USER,ROLE_SERVICE_LIST_ADMIN
jack=jrodriguez,ROLE_USER,ROLE_INVESTIGATION_ADMIN
walter=wkelly,ROLE_USER,ROLE_INVESTIGATION_MGR
anonymous=anonymous,
</value>
</property>
</bean>
<!-- define, but don't use until you're ready to attach to a non-inMemoryDao -->
<bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"><ref local="inMemoryDaoImpl"/></property>
</bean>
<!-- InMemoryDao doesn't encode passwords...it's gotta be plaintext -->
<!-- <property name="passwordEncoder"><ref local="passwordEncoder"/></property> -->
<!-- Automatically receives AuthenticationEvent messages -->
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
<bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref local="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
</bean>
<!-- Essentially Unused unless you're using Basic Authentication, which we're not -->
<bean id="basicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName"><value>Contacts Realm</value></property>
</bean>
<bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key"><value>foobar</value></property>
<property name="userAttribute"><value>anonymousUser,ROLE_ANONYMOUS</value></property>
</bean>
<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key"><value>foobar</value></property>
</bean>
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
</bean>
<!-- ===================== HTTP REQUEST SECURITY ==================== -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
</bean>
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="authenticationFailureUrl"><value>/LoginFailed.html</value></property>
<property name="defaultTargetUrl"><value>/Home.html</value></property>
<property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
</bean>
<bean id="authenticationProcessingFilterEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"><value>/Login.html</value></property>
<property name="forceHttps"><value>false</value></property>
</bean>
<bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"><value>false</value></property>
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
<!-- An access decision voter that reads ROLE_* configuration settings -->
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
<!-- Note the order that entries are placed against the objectDefinitionSource is critical.
The FilterSecurityInterceptor will work from the top of the list down to the FIRST pattern that matches the request URL.
Accordingly, you should place MOST SPECIFIC (ie a/b/c/d.*) expressions first, with LEAST SPECIFIC (ie a/.*) expressions last -->
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
<property name="objectDefinitionSource">
<value>
PATTERN_TYPE_APACHE_ANT
/media/*=ROLE_ANONYMOUS,ROLE_USER
/styles/*=ROLE_ANONYMOUS,ROLE_USER
/AccessDenied.html*=ROLE_ANONYMOUS,ROLE_USER
/Login.html*=ROLE_ANONYMOUS,ROLE_USER
/Logout.html*=ROLE_ANONYMOUS,ROLE_USER
/Login,loginForm.sdirect*=ROLE_ANONYMOUS,ROLE_USER
/LoginFailed.html*=ROLE_ANONYMOUS,ROLE_USER
/Home.html*=ROLE_ANONYMOUS,ROLE_USER
/asset.svc*=ROLE_ANONYMOUS,ROLE_USER
/ManageUsers.html*=ROLE_SYSTEM_ADMIN,ROLE_USER_ADMIN
/**=ROLE_USER
</value>
</property>
</bean>
</beans>
Some notes about this configuration:
1) If you don't create a generic "ROLE_USER" to define someone who isn't anonymous, then you need to add every single role to every single pattern in your filterInvocationInterceptor. That's a pain, so I just have every user defined to be a member of ROLE_USER in addition to their "real" role (e.g. ROLE_SYSTEM_ADMIN).
2) I can't emphasize the passwordEncoder gotchya enough...I lost a whole day trying to figure out why I kept getting redirected to the login page after I swear I correctly logged in. For the purposes of getting up and running, it's easy to use the inMemoryDaoImpl, but just be sure to comment out the use of the PasswordEncoder that you may have cut & pasted from demo code you'll find on this board and others.