Coming Up for Air

Using Acegi Security With JSF

A question that often comes up in when looking through JSF <a target="blank" href="http://forum.java.sun.com/forum.jspa?forumID=427&start=0">forums</a> or idling on IRC is, "How do I secure my JSF app?" to which, of course, there are a myriad of options. At <a target="blank" href="http://www.iec-okc.com">IEC</a>, we use <a target="blank" href="http://www.acegisecurity.org/">Acegi Security</a>. That answers only part of the "how," though, as Acegi is not the easiest thing to learn. In this blog entry, I’ll detail how we have Acegi implemented at IEC. While it’s not perfect, it works well for us, and should be enough to get someone moving in the right direction.

The first step, of course, is to download Acegi, and integrate with the web application. Once the jars have been installed in WEB-INF/lib, web.xml needs to be edited:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext-acegi.xml,</param-value>
</context-param>
<listener>
        <listener-class>
                org.springframework.web.context.ContextLoaderListener
        </listener-class>
</listener>
<listener>
        <listener-class>
                org.acegisecurity.ui.session.HttpSessionEventPublisher
        </listener-class>
</listener>
<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>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
</filter-mapping>

That’s the easy part. The Acegi configuration, applicationContext-acegi.xml in this example, is where the difficulty comes in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- ******************************************************************* -->
    <!-- Acegi Security Config                                               -->
    <!-- ******************************************************************* -->
    <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,rememberMeProcessingFilter,contextHolderAwareRequestFilter,anonymousProcessingFilter,switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            </value>
        </property>
    </bean>
    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="daoAuthenticationProvider" />
                <ref local="anonymousAuthenticationProvider" />
                <ref local="rememberMeAuthenticationProvider"/>
            </list>
        </property>
    </bean>
    <bean id="daoAuthenticationProvider" class="class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"">
        <property name="userDetailsService">
            <ref local="memoryAuthenticationDao" />
        </property>
    </bean>
    <bean id="memoryAuthenticationDao" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
        <property name="userMap">
            <value>
                user1=ROLE_FOO, ROLE_ADMIN
            </value>
        </property>
    </bean>
    <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>
    <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>
    <bean id="contextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" />
    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
        <property name="authenticationManager"><ref local="authenticationManager"/></property>
        <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
    </bean>
    <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
        <property name="userDetailsService"><ref local="memoryAuthenticationDao"/></property>
        <property name="key"><value>springRocks</value></property>
    </bean>
    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
        <property name="key"><value>springRocks</value></property>
    </bean>
    <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="defaultTargetUrl"><value>/index.jsf</value></property>
        <property name="alwaysUseDefaultTargetUrl"><value>false</value></property>
        <property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
        <property name="authenticationFailureUrl"><value>/login.jsp?login_error=1</value></property>
        <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
    </bean>
    <bean id="authenticationProcessingFilterEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
        <property name="loginFormUrl"><value>/login.jsp</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>
    <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>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /foo*=ROLE_FOO
            </value>
        </property>
    </bean>
    <bean id="switchUserProcessingFilter" class="org.acegisecurity.ui.switchuser.SwitchUserProcessingFilter">
        <property name="userDetailsService" ref="memoryAuthenticationDao" />
        <property name="switchUserUrl"><value>/j_acegi_switch_user</value></property>
        <property name="exitUserUrl"><value>/j_acegi_exit_user</value></property>
        <property name="targetUrl"><value>/</value></property>
    </bean>
    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter" />
</beans>

I’m not Acegi expert, and I make no claims to understand what all is going on here, but I have included the whole config file as I found it difficult (at the time, at least) to find a complete example that uses the Acegi 1.x package and class names. I must also note that I’ve done my best to back out IEC-specific changes, so there may still remain same changes that need to be made to get this to work in a "clean" environment (read as: this should work, but it may not. If you have to make changes, please let me know and I’ll fix my example).

Once Acegi is setup and configured, we can start protecting resources. The configuration above protects all URIs that start with /foo, but it is also sometimes desirable to protect only certain parts of a page. Acegi ships with some JSP tags that make that possible, but these work outside the JSF lifecycle. To solve that problem, Cagatay Civici has written some JSF tags that do live inside that cycle.

Here’s an example from an app we have in poroduction. In this particular snippet, if the user has the correct permissions, we allow him to approve a request or resubmit the order:

1
2
3
4
5
6
7
8
9
10
<acegijsf:authorize ifAllGranted="ROLE_OrderManager]
    <h:form id="requestForm" rendered="#{dwmoForm.isRequest == true}"
        style="display: inline">
        <input type="button" value="Approve Request"
            onclick="return approveRequest();" />
    </h:form>
    <h:commandButton id="resubmitButton" type="button"
        value="Resubmit Order" onclick="return resubmitOrder();"
        style="display: inline; margin: 0px; padding: 0px;"/>
</acegijsf:authorize>

And that’s all there is to it. Once you get it setup, it’s really not too difficult to work with.

I have seen some balk at using Acegi, given its dependence on Spring, but, while it’s true that you must have Spring in your classpath for Acegi to work, by no means does that require that the application itself be Spring-based. In fact, we’re using this very approach in an application that uses no Spring at all, but, rather, some EJB3 session beans (and Ajax on the front end). So, if you can live with the extra few jars to solve the dependencies of Acegi, it plays well JSF, even in a non-Spring app.

What are your thoughts? Do you see ways to improve this approach, or do you have a better one altogether? I’d love to hear your feedback.

Quotes

Sample quote

Quote source