by Seema Richard

Acegi security for JSF applications

how-to
Apr 15, 200823 mins

Use the Spring application context to secure your JSF applications

Acegi security is a natural choice for many enterprise applications built using the Spring framework. JSF (JavaServer Faces) is a Java-based Web application framework that facilitates rapid application development by assembling reusable UI components. As you will learn in this article, it is not hard to secure your JSF applications with Acegi. In fact, you can address most details of authentication and authorization in a Spring application context file.

I’ll present a simple Web application example based on the Apache MyFaces JSF implementation. I’ll then show you how to use the Spring application context to integrate Acegi’s authentication and authorization functionality into the JSF application. You’ll see how to implement Acegi’s URL-level role-based authorization, and you’ll also learn how to implement Acegi’s business-layer security using Java 5 annotations.

Note that this article is intended for Java developers already familiar with JavaServer Faces, Acegi, and Spring-based application development. You will be introduced to securing JSF applications with Acegi.

About the sample application

The sample application used for this discussion presents a login page to the user. If the user attempts to log in with the correct user name and password, he is redirected to a page where he may make a purchase. We will use the Acegi security framework to configure the security details that ensure the user’s ID has been authenticated, and that his presence on the purchase page is authorized. The user can visit the purchase page if has the role ROLE_URLACCESS. But making a purchase is a secure business method, so the user will only be able to access the purchase page if he has the role ROLE_ALLACCESS.

The application utilizes Acegi’s support for Java 5 security annotations, so Java 5 is a pre-requisite for following the example. You will need the JAR files of Acegi, MyFaces, and Spring in your WEB-INF/lib folder if you want to run the application.

Now let us see how we go about configuring the Spring application context to integrate Acegi and JSF.

Integrating Acegi and JSF

Before attempting to integrate Acegi with JSF, it is important to understand how Acegi works in the Web layer. Acegi uses servlet filters for authentication and authorization. The filters can be chosen and configured in the order in which they need to be executed. The appropriate filter fetches authentication request information such as username and password, and passes this information to Acegi’s authentication manager, which is configured as a Spring bean.

The authentication manager contacts an authentication provider to get a UserDetails object from a UserDetailsService. If authentication is successful, the user’s granted authorities (application-wide permissions) are populated in the returned UserDetails object. This information is used to build the authentication object, which is then stored in Acegi’s security context (which is created by yet another filter). The security context is associated with the current execution thread by the security context holder. The security context is stored as a session attribute and used for authorization checks by Acegi in the Web layer.

I assume you are familiar with basic JSF application development, and so will not describe the steps involved in configuring a JSF application, such as defining the JSF servlet and providing the servlet mapping. I also assume, for the sake of this example, that we have already registered a ContextLoaderListener to load the Spring application context when the application starts up. Download the sample application code for details.

Assuming that the above configurations are complete, the first step toward integrating Acegi with JSF is to configure Acegi’s filters and direct all requests and forwards to be routed through them.

Enabling the Acegi filter chain in web.xml

Instead of registering all the Acegi filters individually in web.xml, you can use Acegi’s security filter chain proxy. The Acegi filter chain invokes the filters added to its property list in the Spring context file. Acegi’s FilterToBeanProxy class implements the javax.servlet.Filter interface; hence its doFilter() method is invoked by the servlet container when the request for a JSF page is received. This object delegates filter requests to a Spring-managed bean of type FilterChainProxy that is defined in the Spring bean context.

The configuration in web.xml is as shown in Listing 1.

Listing 1. Configuring the Acegi filter chain

<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>
                                    

The mapping in Listing 2 causes the requests and forwards to be routed through Acegi’s filter chain proxy.

Listing 2. Mapping requests and forwards to the filter

<filter-mapping>
                                       <filter-name>Acegi Filter Chain Proxy</filter-name>
                                       <url-pattern>/*</url-pattern>
                                       <dispatcher>FORWARD</dispatcher>
                                       <dispatcher>REQUEST</dispatcher>
                                       </filter-mapping>
                                    

Configuring the filter chain proxy

The FilterChainProxy class is responsible for executing the chain of Acegi’s security filters. In the FilterChainProxy bean definition, the possible URI patterns are mapped with the filters that need to be invoked for requests matching the pattern. The more-specific URI patterns are to be specified earlier in the list, because the first-matching pattern mapping is considered when a request arrives. The filters are to be specified in the correct order. If no filters are to be applied for any URI pattern, the string #NONE is specified.

Listing 3. Bean definition for FilterChainProxy

<bean id="filterChainProxy"
                                       class="org.acegisecurity.util.FilterChainProxy">
                                       <property name="filterInvocationDefinitionSource">
                                       <value>
                                       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                                       PATTERN_TYPE_APACHE_ANT
                                       /error/*=#NONE#
                                       /back*=#NONE#
                                       /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
                                       </value>
                                       </property>
                                       </bean>
                                    

We will discuss the various filters configured in the proxy in the coming sections.

Setting up authentication

Acegi has an authentication-processing filter that is responsible for processing the authentication form, retrieving the username and password, and sending it to the AuthenticationManager for verification.

Before setting up the authentication processing filter, we need a login page, which we’ll use to collect user credentials and pass them to the filter. This page is served by Acegi whenever the user attempts to access a protected resource without authentication.

The login page

The login page shown in Listing 4 uses MyFaces tomahawk components. The id of the username component must be “j_username” and the id of the password must be “j_password,” as expected by the Acegi AuthenticationFilter.

Listing 4. Login page for the example application

<%@
                                       taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
                                       <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
                                       <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>
                                       <html>
                                       <head>
                                       <title>System Login</title>
                                       </head>
                                       <body>
                                       <f:view>
                                       <h:form>
                                       <h:panelGrid columns="2">
                                       <h:outputLabel value="User Name" for="j_username" />
                                       <t:inputText id="j_username" forceId="true"
                                       value="#{loginBacking.userId}" size="40" maxlength="80"></t:inputText>
                                       <h:outputLabel value="Password" for="j_password" />
                                       <t:inputSecret id="j_password" forceId="true"
                                       value="#{loginBacking.password}" size="40" maxlength="80"
                                       redisplay="true"></t:inputSecret>
                                       </h:panelGrid>
                                       <h:commandButton action="login" value="Login" />
                                       <h:messages id="messages" layout="table" globalOnly="true"
                                       showSummary="true" showDetail="false" />
                                       </h:form>
                                       </f:view>
                                       </body>
                                       </html>
                                    

Defining the backing bean

In the login page above, we bound the user name and password values to the userId and password properties of a backing bean. Defining this backing bean class and configuring it in the application’s faces-config.xml file is the next step.

Listing 5. Backing bean definition

.....
                                       public class LoginBacking  {
                                       
                                       // properties
                                       private String userId;
                                       
                                       private String password;
                                       
                                       .......
                                       
                                       }
                                    

Here is the entry for the backing bean in faces-config.xml:

Listing 6. Login backing bean in faces-config.xml

<managed-bean>
                                       <managed-bean-name>loginBacking</managed-bean-name>
                                       <managed-bean-class>acegitest.backing.LoginBacking</managed-bean-class>
                                       <managed-bean-scope>request</managed-bean-scope>
                                       </managed-bean>
                                    

In the login page, you can see that when the Login button is clicked, the action invoked is “login.” We need to map this action to the URL that the authentication filter will respond to. (We’ll also configure this URL in the Spring context as the filterProcessesUrl property of the filter, as detailed in the next section.) We specify the navigation rule in faces-config.xml as shown in Listing 7.

Listing 7. Specifying a navigation rule in faces-config.xml

<navigation-rule>
                                       <from-view-id>/acegilogin.jsp</from-view-id>
                                       <navigation-case>
                                       <from-outcome>login</from-outcome>
                                       <to-view-id>/j_acegi_security_check.jsp</to-view-id>
                                       </navigation-case>
                                       </navigation-rule>
                                    

Authentication filters

Next we’ll set up the Acegi filters that handle the authentication process: HttpSessionContextIntegrationFilter and AuthenticationProcessingFilter.

HttpSessionContextIntegrationFilter

Acegi stores security information in a security context. The HttpSessionContextIntegrationFilter is used to store the SecurityContext object in the session, so that it is available across requests. It is configured as shown in Listing 8.

Listing 8. Configuring the HttpSessionContextIntegrationFilter

<bean id="httpSessionContextIntegrationFilter"
                                       class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
                                    

The SecurityContext is associated with the current executing thread by a SecurityContextHolder using a ThreadLocal. This filter sets up the SecurityContext in the SecurityContextHolder when the request arrives, and updates the session with the changes made to the SecurityContext at the end of the request.

AuthenticationProcessingFilter

This filter receives the Authentication object from the manager and stores it in the SecurityContext. Its configuration is shown in Listing 9.

Listing 9. Configuring the AuthenticationProcessingFilter

<bean id="authenticationProcessingFilter"
                                       class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
                                       <property name="filterProcessesUrl">
                                       <value>/j_acegi_security_check.jsp</value>
                                       </property>
                                       <property name="authenticationFailureUrl">
                                       <value>/acegilogin.jsf</value>
                                       </property>
                                       <property name="defaultTargetUrl">
                                       <value>/hello.jsf</value>
                                       </property>
                                       <property name="authenticationManager">
                                       <ref bean="authenticationManager" />
                                       </property>
                                       </bean>
                                    

If authentication was successful, the redirection happens to the defaultTargetUrl. In the case of an unsuccessful authentication, the user is redirected to the authenticationFailureUrl, which is the login page itself. This allows us to take advantage of Acegi’s ability to automatically navigate to the login page whenever it is required. The URL which triggers this filter is given by the filterProcessesUrl. This is how the authentication filter gets invoked when the user submits the login page.

Next, we’ll take a look at the authentication manager, which is wired as a property of the AuthenticationProcessingFilter.

Authentication management

Acegi’s authentication manager is responsible for processing authentication requests sent by the authentication filter. It attempts to authenticate a user by using an appropriate authentication provider to check his credentials. This manager returns an Authentication object populated with roles or permissions, or any other granted authorities of the principal user if the authentication was successful. Listing 10 shows the bean definition for the authentication manager.

Listing 10. AuthenticationManager bean definition

<bean id="authenticationManager"
                                       class="org.acegisecurity.providers.ProviderManager">
                                       <property name="providers">
                                       <list>
                                       <ref local="daoAuthenticationProvider" />
                                       </list>
                                       </property>
                                       </bean>
                                    

The authentication manager utilizes a DaoAuthenticationProvider provided by Acegi that is responsible for authenticating against a database or some other repository.

Listing 11. DaoAuthenticationProvider bean definition

<bean
                                       id="daoAuthenticationProvider"
                                       class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
                                       <property name="userDetailsService" ref="userDetailsService" />
                                       </bean>
                                    

Loading user details

Any implementation of Acegi’s UserDetailsService interface can provide data access services to the DaoAuthenticationProvider. This interface has just one method — loadUserByUsername — that locates the user based on the given username.

Our next step is to configure and implement a custom UserDetailsServiceImpl class, which will retrieve the matching user details from a userRepository bean (this is a DAO that can fetch the data from a database or some other data store) into Acegi’s UserDetails object and return it.

Listing 12. Configuring the custom UserDetailsService implementation

<bean id="userDetailsService"
                                       class="acegitest.UserDetailsServiceImpl">
                                       <constructor-arg ref="userRepository" />
                                       </bean>
                                       
                                       <bean id="userRepository" class="acegitest.UserDaoImpl">
                                       </bean>
                                    

Let’s take a look at the implementation classes required for the above configuration.

AppUser

The AppUser class encapsulates user information specific to the application. Each user has a first name, last name, login ID, password, and a set of security roles.

Listing 13. AppUser

public class AppUser implements
                                       Serializable {
                                       private String firstName;
                                       
                                       private String lastName;
                                       
                                       private String login;
                                       
                                       private String password;
                                       
                                       private Set<String> roles;
                                       ...
                                       }
                                    

UserDao

The UserDao class defines a findUser method that takes the user ID as its argument and returns the corresponding user details encapsulated in an AppUser object. For the sake of simplicity, the user details have been hard-coded in the UserDaoImpl class in Listing 14. We have a user “john” with the role ROLE_URLACCESS, a user “jim” with no roles, and a user “tina” with the role ROLE_ALLACCESS.

Listing 14. UserDao implementation

public class UserDaoImpl implements
                                       UserDao {
                                       public AppUser findUser(String userName) {
                                       AppUser appUser = null;
                                       Set<String> roles = new HashSet<String>();
                                       if (userName.equals("john")) {
                                       roles.add("ROLE_URLACCESS");
                                       appUser = new AppUser("john", "John", "Turner", "john", roles);
                                       } else if (userName.equals("jim")) {
                                       appUser = new AppUser("jim", "Jim", "Daniel", "jim", roles);
                                       } else if (userName.equals("tina")) {
                                       roles.add("ROLE_ALLACCESS");
                                       appUser = new AppUser("tina", "Tina", "Joseph", "tina", roles);
                                       }
                                       return appUser;
                                       }
                                       ...
                                       }
                                    

UserDetailsServiceImpl

Next we’ll define the UserDetailsServiceImpl class that is responsible for getting the AppUser object from the DAO and converting it into Acegi’s UserDetails object. Acegi’s GrantedAuthority object represents an authority granted to the authenticated principal. Here, the roles to which the user belongs are the granted authorities. All the required details such as user ID, password, roles, and so on are extracted from the user’s AppUser object and populated into the appropriate fields of Acegi’s UserDetails object. They are then sent back for use by the authentication manager.

Listing 15. UserDetailsService implementation

public class UserDetailsServiceImpl implements UserDetailsService {
      private UserDao userDao;
      public UserDetailsServiceImpl(UserDao userDao) {
            this.userDao = userDao;
      }
      public UserDetails loadUserByUsername(String username)
                  throws UsernameNotFoundException, DataAccessException {
            AppUser user = userDao.findUser(username);
            if (user == null)
                  throw new UsernameNotFoundException("User not found: " + username);
            else {
                  return makeAcegiUser(user);
            }
      }
      
      private org.acegisecurity.userdetails.User makeAcegiUser(AppUser user) {
            return new org.acegisecurity.userdetails.User(user.getLogin(), user
                        .getPassword(), true, true, true, true,
                        makeGrantedAuthorities(user));
      }

      private GrantedAuthority[] makeGrantedAuthorities(AppUser user) {
            GrantedAuthority[] result = new GrantedAuthority[user.getRoles().size()];
           int i = 0;
            for (String role : user.getRoles()) {
                  result[i++] = new GrantedAuthorityImpl(role);
            }
            return result;
      }
}                                    

Handling a failed authentication

The authentication manager attempts to returns a fully populated Authentication object (including granted authorities) if authentication was successful. If the authentication token passed by the authentication filter does not match with the obtained user information, then a BadCredentialsException must be thrown. We can add the following code to the login backing bean in order to present an error message to the user:

Listing 16. Create the error message to be displayed if credentials are incorrect

public LoginBacking() {
                                       
                                       Exception ex = (Exception) FacesContext
                                       .getCurrentInstance()
                                       .getExternalContext()
                                       .getSessionMap()
                                       .get(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY);
                                       
                                       if (ex != null)
                                       FacesContext.getCurrentInstance().addMessage(
                                       null,
                                       new FacesMessage(FacesMessage.SEVERITY_ERROR, ex
                                       .getMessage(), ex.getMessage()));
                                       
                                       }
                                    

If the authentication is successful, the user is directed to the page marked as the defaultTargetUrl property of the authentication filter, which in our case is hello.jsf.

URL-level authorization

Our next step is to enable authorization in the JSF application. For this we need to set up Acegi’s authorization filter.

FilterSecurityInterceptor

This filter is enabled for role-based authorization of URLs. It intercepts calls to a secure URL and checks whether the caller is authorized to access the URL. It extends AbstractSecurityInterceptor, which has the logic to retrieve the Authentication object from the SecurityContextHolder and authorize requests against the AccessDecisionManager. URL-role mapping is configured in the objectDefinitionSource property, as shown in Listing 17.

Listing 17. FilterSecurityInterceptor configuration

<bean id="filterInvocationInterceptor"
                                       class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
                                       <property name="authenticationManager"
                                       ref="authenticationManager" />
                                       <property name="accessDecisionManager"
                                       ref="accessDecisionManager" />
                                       <property name="objectDefinitionSource">
                                       <value>
                                       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                                       PATTERN_TYPE_APACHE_ANT
                                       /acegilogin.jsf=IS_AUTHENTICATED_ANONYMOUSLY
                                       /**=ROLE_ALLACCESS,ROLE_URLACCESS
                                       </value>
                                       </property>
                                       </bean>
                                    

In the above configuration, only the roles ROLE_ALLACCESS and ROLE_URLACCESS have access to pages other than the login page. Because the user is not authenticated when accessing the login page, anonymous users are given access to this URL.

The AccessDecisionManager defined below decides if the caller is to be granted access to the protected URL. To make the decision, the AccessDecisionManager checks access control votes from a set of voters of type AccessDecisionVoter. The AccessDecisionManager is defined to be of type AffirmativeBased, meaning that access will be given to the caller if any voter casts an affirmative vote.

Working with voters

Acegi provides many AccessDecisionVoter implementations. Here we have used the RoleVoter and AuthenticatedVoter. The RoleVoter allows access if the caller’s security context has the role specified against the URL in the objectDefinitionSource. The AuthenticatedVoter checks if one of the predefined roles — IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, or IS_AUTHENTICATED_ANONYMOUSLY — is present in the role list. In our case, the objectDefinitionSource object allows the login page to be accessed by an anonymously authenticated user. AuthenticatedVoter thus votes access-granted; otherwise, it would vote access-denied.

Listing 18. AccessDecisionManager configuration

<bean id="accessDecisionManager"
                                       class="org.acegisecurity.vote.AffirmativeBased">
                                       <property name="allowIfAllAbstainDecisions" value="false" />
                                       <property name="decisionVoters">
                                       <list>
                                       <bean class="org.acegisecurity.vote.RoleVoter" />
                                       <bean class="org.acegisecurity.vote.AuthenticatedVoter" />
                                       </list>
                                       </property>
                                       </bean>
                                    

For anonymous authentication to work, we must also configure the AnonymousProcessingFilter in the Spring context.

AnonymousProcessingFilter

This filter populates the Acegi SecurityContext with an anonymous authentication token if it detects that authentication has not taken place. The configuration is shown in Listing 19.

Listing 19. Configuring the AnonymousProcessingFilter

<bean id="anonymousProcessingFilter"
                                       class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
                                       <property name="key" value="changeThis" />
                                       <property name="userAttribute"
                                       value="anonymousUser,ROLE_ANONYMOUS" />
                                       </bean>
                                    

The value of the userAttribute is set to the predefined roles for anonymous users.

Redirecting unauthorized users

If a user accesses a secure URL for whch he does not have access privilege, he should be redirected to an error page. For this we use Acegi’s ExceptionTranslationFilter.

In the case of an attempt to access an unauthorized URL, Acegi’s ExceptionTranslationFilter can cause any configured handlers to be invoked. To send the user to the error page, we configure the AccessDeniedHandler as shown in Listing 20.

Listing 20. Configuring the AccessDeniedHandlers for exceptions

<bean id="exceptionTranslationFilter"
                                       class="org.acegisecurity.ui.ExceptionTranslationFilter">
                                       <property name="authenticationEntryPoint">
                                       <bean
                                       class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                                       <property name="loginFormUrl" value="/acegilogin.jsf" />
                                       <property name="forceHttps" value="false" />
                                       </bean>
                                       </property>
                                       <property name="accessDeniedHandler">
                                       <bean
                                       class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
                                       <property name="errorPage" value="/accessDenied.jsf" />
                                       </bean>
                                       </property>
                                       </bean>
                                    

As is obvious, the loginFormUrl property value is the URL of the login page itself. If user authorization fails due to insufficient permissions, an AccessDeniedException is thrown by the Acegi security framework. If this happens, the accessDeniedHandler forwards the request to the URL specified by the errorPage property of the handler. In this case, the user is forwarded to accessDenied.jsf, which displays an appropriate error message.

Handling user logout

We need a logout link on secure pages to enable the user to log out of our JSF application. The following line can be added to a JSF page to add a command button that provides logout functionality:

<h:commandButton action="logout" value="Logout" immediate="true"/>
                                    

The action attribute of the command button is defined as “logout“. We can map this outcome to the view /j_acegi_logout.jsp using the following navigation rule:

Listing 20. A navigation rule for logout

<navigation-rule>
                                       <from-view-id>*</from-view-id>
                                       <navigation-case>
                                       <from-outcome>logout</from-outcome>
                                       <to-view-id>/j_acegi_logout.jsp</to-view-id>
                                       </navigation-case>
                                       </navigation-rule>
                                    

Next, we need to configure the logout filter.

LogoutFilter

The logout filter is used to log out a principal user by triggering a set of logout handlers. It is configured as shown in Listing 21.

Listing 21. Logout filter

<bean
                                       id="logoutFilter"
                                       class="org.acegisecurity.ui.logout.LogoutFilter">
                                       <constructor-arg value="/index.jsp" />
                                       <constructor-arg>
                                       <list>
                                       <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
                                       </list>
                                       </constructor-arg>
                                       <property name="filterProcessesUrl">
                                       <value>/j_acegi_logout.jsp</value>
                                       </property>
                                       </bean>
                                    

The constructor arguments are the logout success URL (the URL to redirect to after logout) and a list of logout handlers (which execute the actual logout process). The filterProcessesUrl is the URL that activates this filter, which in our case is /j_acegi_logout.jsp.

Securing business methods

In addition to securing URLs, we can also protect business methods from unauthorized access, by using Acegi’s MethodSecurityInterceptor. It intercepts method calls and uses the authenticationManager and accessDecisionManager to check whether the user is authorized to invoke secure methods. MethodSecurityInterceptor can be defined in the Spring context as follows:

Listing 22. MethodSecurityInterceptor bean definition

<bean id="securityInterceptor"
                                       class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
                                       <property name="validateConfigAttributes">
                                       <value>false</value>
                                       </property>
                                       <property name="authenticationManager">
                                       <ref local="authenticationManager" />
                                       </property>
                                       <property name="accessDecisionManager">
                                       <ref local="accessDecisionManager" />
                                       </property>
                                       <property name="objectDefinitionSource">
                                       <ref local="objectDefinitionSource" />
                                       </property>
                                       </bean>
                                    

As mentioned above, the security interceptor takes three properties — authenticationManager, accessDecisionManager, and objectDefinitionSource. The ObjectDefinitionSource defined in Listing 23 is responsible for returning a ConfigAttributeDefinition object that contains all of the configuration attributes associated with a single secure method.

Listing 23. ObjectDefinitionSource bean definition

<bean id="objectDefinitionSource"
                                       class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
                                       <property name="attributes">
                                       <ref local="attributes" />
                                       </property>
                                       </bean>
                                       
                                       <bean id="attributes"
                                       class="org.acegisecurity.annotation.SecurityAnnotationAttributes" />
                                    

Using Acegi’s security annotations

We also need to mark business methods to be secured using Acegi’s Java 5-enabled @Secured annotation. The Java 5 Attributes metadata implementation for secure method interception will return security configuration for classes described using this annotation.

In Listing 24 we define the roles allowed to execute the buy() method using Acegi’s @Secured annotation.

Listing 24. Who can access ShoppingService?

public interface ShoppingService {
                                       @Secured({"ROLE_ALLACCESS"})
                                       public void buy(String productId);
                                       }
                                    

Based on the configuration in Listing 24, the buy() method of ShoppingService may be invoked only by users having the role ROLE_ALLACCESS.

Our final step in securing the business method is to configure a MethodDefinitionSourceAdvisor, which may be used with Spring’s DefaultAdvisorAutoProxyCreator to automatically chain the security interceptor to secured-service invocations.

Listing 25. MethodDefinitionSourceAdvisor bean definition

<bean id="autoproxy"
                                               class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
                                       </bean>
                                               
                                       <bean id="methodSecurityAdvisor"
                                           class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"
                                               autowire="constructor">
                                       </bean>
                                    

Invoking secure business methods

Now let us see the backing bean that invokes the protected method of ShoppingService. It receives the productId entered by the user and passes it to the ShoppingService, which is injected as its instance variable. If an unauthorized user invokes the method, the AccessDeniedException is thrown and the user is redirected to the navigation outcome “accessDenied,” which is mapped to an appropriate error page in the faces-config.xml.

Listing 26. ShoppingService’s backing bean

public
                                       class ShoppingBacking {
                                       
                                           private String productId;
                                       
                                           private ShoppingService shoppingService;
                                       
                                           public String send() {
                                               try {
                                       
                                                   shoppingService.buy(productId);
                                               } 
                                               catch (AccessDeniedException e) {
                                                   return ("accessDenied");
                                               }
                                           }
                                           ...
                                       }
                                    

The Buy page in the example application accepts the product ID from the user in an inputText component. The value property of this component is mapped to the productId property of the ShoppingBacking bean in Listing 26. When the “Buy” button is clicked, the value of the product ID is submitted to the backing bean.

Listing 27. Buy page for the example application – hello.jsp

<%@ taglib uri="http://java.sun.com/jsf/html"
                                       prefix="h"%>
                                       <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
                                       <html>
                                       <head>
                                       <title>Buy Page</title>
                                       </head>
                                       <body>
                                       <f:view>
                                           <h:form id="form">
                                               <h:panelGrid id="grid" columns="1">
                                                   <h:outputText value="HELLO #{sessionScope.ACEGI_SECURITY_LAST_USERNAME}" /> 
                                                   <h:outputText id="output1" value="Please enter the product id" />
                                                   <h:inputText id="input1" value="#{shoppingBacking.productId}"
                                                       required="true" />
                                                   <h:commandButton id="button1" value="Buy"
                                                       action="#{shoppingBacking.send}" />
                                                   <h:message id="message1" for="input1" />
                                       
                                                   <h:commandButton action="logout" value="Logout" immediate="true"/>
                                               </h:panelGrid>
                                           </h:form>
                                       </f:view>
                                       </body>
                                       </html>
                                    

The Acegi-secured JSF application is now complete. If you try to access the application’s buy page at https://localhost:8080/AcegiJSFApp/hello.jsf you are automatically redirected to the login page. If you log in as the user “john” you will be able to view the purchase page, because “john” has the role ROLE_URLACCESS. Note, however, that you cannot actually make a purchase because access to this business method is only allowed for the role ROLE_ALLACCESS. If you log in as the user “tina” you will be able to make a purchase, because she has the role ROLE_ALLACCESS. Finally, if you log in as an unauthorized user you will come to the application’s access-denied page.

In conclusion

Acegi’s portability and configurability have made it a popular security framework in a very short span of time, and there is no reason you should not be able to use it to secure your JSF applications. In this article you have seen how to use simple configurations and minimal coding to enable Acegi filters for authentication and role-based authorization. You’ve also had a quick look at using Acegi’s Java 5-enabled @Secured annotation to secure business services that are exposed as Spring beans.

You can download the source code for the sample application from the Resources section. The sample application was developed in Eclipse 3.2 and deployed and tested on Apache Tomcat 5.5.

Seema Richard is a Java architect at the Trivandrum-based software company UST Global. She has been working in Java for the last 8 years, specialising in the J2EE technology spectrum. She has a B.Tech from College of Engineering, Trivandrum and a Diploma in Advanced Computing from CDAC, Pune. She also holds Sun certifications for Programmer, Web Component Developer and Business Component Developer.