Saturday, July 7, 2007

Step by step Spring-Security (aka. Acegi security)

I recently evaluated the use of Acegi as the security framework for a Web development project. In the end, we decided to move forward with Acegi but in the beginning it took a couple days to come to that decision. The amazing thing is: once you get over the initial learning curve, it's smooth sailing. Hence, I wanted to share my experiences with it because first, I wanted to expose the Acegi security framework to JDJ readers and, second, I wanted to make it easier for JDJ readers to get over the initial learning curve. Once you're over that, you should be well on your way with Acegi.

Exposing Acegi Security Framework

Acegi is an open source security framework that allows you to keep your business logic free from security code. Acegi provides three main types of security:

  1. Web-based access control lists (ACL) based on URL schemes
  2. Java class and method security using AOP
  3. Yale's Central Authentication Service for single sign-on (SSO).
Acegi also provides the option of performing container security.

Acegi uses Spring as its configuration settings, so those familiar with Spring will be at ease with Acegi configuration. If you're not familiar with Spring, it's still easy to learn Acegi configuration by example. You don't have to use SpringMVC to secure your Web application. I have successfully used Acegi with Struts. You can use Acegi with WebWork and Velocity, Struts, SpringMVC, JSF, Web Services, and more.

Why use Acegi instead of JAAS? It can be difficult to stray from well-documented standards like JAAS. However, porting container-managed security realms is not easy. With Acegi, this security layer is an application framework that is easily ported. Acegi will allow you to easily reuse and port your "Remember Me," "I forgot my password," and log-in/log-out security functions to different servlet and EJB containers. If you have a standards-based security layer that you have re-created for numerous Java applications and it is not getting reused, you need to take a good look at Acegi. Besides, why are you spending time on framework coding when you should be focusing on the business logic? Leave the framework development to product developers and the open source community.

Getting Over That Initial Learning Curve

To get you over the initial learning curve, I'll take you through a simple setup using a demonstration application. I'll focus on the first security approach - URL-based security for Web applications because that's the most commonly used.

Installation

First things first - we need to install it! I'll use Tomcat 5 as my servlet container to illustrate.

Step 1: Set up a new Tomcat Web context with the "WEB-INF/", "WEB-INF/lib/", and "WEB-INF/classes" folders per usual . I called my context "/acegi-demo" and access it using http://localhost:8080/acegi-demo/.

Step 2: Add another folder called "/secured," which we'll protect with Acegi.

Step 3: Now let's add the necessary Acegi library files to plug-in Acegi to our Tomcat context. (Please download the acegi-demo.zip file provided with this article .)

Let's understand the JAR packages we are adding to the lib directory. The most important JAR is acegi-security-0.8.3.jar, the Acegi core library. Acegi leverages Spring for its configuration, so we also need spring-1.2.RC2.jar. The remaining JARs are utilities libraries for dealing with collections (commons-collections-3.1.jar), logging (commons-logging-1.0.4.jar, log4j-1.2.9.jar), and regular expressions (oro-2.0.8.jar). Special thanks to Apache Jakarta for these wonderful utility libraries.

Configuration

Now that we have our core infra-structure in place, let's focus on configuration.

Step 4: Configure the web.xml file to begin tying the Web application to the Acegi security framework.

  1. First, we need to set up two parameters: contextConfiguration, which will point to Acegi's configuration file, and log4jConfigLocation, which will point to Log4J's configuration file.
  2. Next, we have to set up the Acegi Filter Chain Proxy; this critical proxy allows Acegi to interact with the servlet filtering feature. We will talk about this more in step 5 (configuring applicationContext.xml).
  3. Finally, we want to add three listeners to loosely couple Spring with the Web context, Spring with Log4J and Acegi with the HTTP Session events in the Web context, such as create session and destroy session.
Step 5: Now we need to configure the applicationContext.xml  to instruct the Acegi framework to perform our security requirements. It is important to note that you typically don't have to write or compile any code to fuse your application with the Acegi security framework. Acegi is almost entirely configuration driven, thanks to a great design by its creator, Ben Alex, and Spring. Okay, enough back patting, let's get to it...

Remember, the Acegi Filter Chain Proxy is critical. This is the backbone of the configuration. Using the servlet filter specification, Acegi is able to plug in its security functionality in a modular way.

I ordered the Spring bean references in the applicationContext.xml file based on the sequence each bean is referenced, starting with the filterChainProxy bean. If you are new to Spring, just know that the order in which a bean is referenced is not important. I ordered it this way to make it as easy as possible to follow along.

<bean id="filterChainProxy" class="net.sf.acegisecurity.util.FilterChainProxy">
    <property name="filterInvocationDefinitionSource">
     <value>
     CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
     PATTERN_TYPE_APACHE_ANT
   /**=httpSessionContextIntegrationFilter, authenticationProcessingFilter,
   anonymousProcessingFilter, securityEnforcementFilter
     </value>
    </property>
   </bean>

In the filterChainProxy bean (see code snippet above), we tell Acegi that we want to use lowercase for all URL comparisons and use the Apache ANT style for pattern matching on the URLs. In our example, we run the filterChainProxy on every single URL by specifying /**=Filter1,Filter2, etc. Next, we set up the filter chain itself, where order is very important. We have four filter chains in this simple example, but when you start using Acegi, you'll most likely have more. Viewing applicationContext.xml, please take a few moments to follow all the bean references in great detail as you traverse the filter chain. I will walk through each item in the filter chain at a high level.

The first item in the chain must be the httpSessionContextIntegrationFilter filter. This filter works hand-and-hand with the HTTP Session object and the Web context to see if the user is authenticated and, if so, then what roles the user has. We have little to configure for this filter.

The second item in the chain is the authenticationProcessingFilter filter, which searches for any URL that matches /j_acegi_security_check because this is the URL that our login form will post a username and password to when attempting authentication. This filter also contains the configuration information detailing where to send someone if the login succeeds or fails. If it succeeds, you can configure this filter to direct the user to the page the user originally tried to access or direct the user to a particular start page where you want all authenticated users to land after authentication. I have the latter option configured in my example by setting alwaysUseDefaultTargetUrl to true and you just set it to false to get the former option.

One of the beans configured in the authenticationProcessingFilter is the authenticationManager bean. This bean manages the various providers you configure. A provider is essentially a repository of usernames with corresponding passwords and roles. The authenticationManager will stop iterating through the list of providers once a user is successfully authenticated. In practice, you may have two or three providers; for example, one provider could access an Active Directory for employee credentials, while your second provider might access a database for customer credentials. You will most often need an anonymousAuthenticationProvider because you need it to allow access to pages that do not requiring authentication to access, such as the login page or the home page. The demonstration application for this article uses a memory provider and an anonymous provider. Once you get this simple application working, you probably want to add a JDBC or LDAP provider.

The third item in the chain is the anonymousProcessingFilter filter. This will match the value created by the anonymousAuthenticationProvider.

The fourth and final item in the filter chain is the securityEnforcementFilter filter. This filter has two beans: the filterSecurityInterceptor and the authenticationProcessingFilterEntryPoint. The latter bean is used to direct the user to the login form each time the user tries to access a secured page but is not logged in. We can also force the user to use HTTPS. The former bean, filterSecurityInterceptor, does quite a bit of heavy lifting by tying all our filters together.

The filterSecurityInterceptor bean checks that the authenticated user has the right roles (or permissions) to access a particular objectDefinitionSource. Here we are using AffirmativeBased voting, which means the user just has to have one of the roles specified in the objectDefinitionSource. This is most likely what you will use, but Acegi does have a unanimous voter that ensures that a person has every role specified in the objectDefinitionSource before granting access. By now you may have realized that objectDefinitionSource determines who can access what.

The objectDefinitionSource starts off with the same two configuration instructions that filterChainProxy did, namely converting all URLs to lowercase and using the Apache ANT style for regular expressions. Next, we define which roles are allowed to access a particular URL. In our example, we give anonymous access to the /acegilogin.jsp page so that unauthenticated users can arrive at this page to log in. The next line in the objectDefinitionSource provides access to everything below the /secured directory for any user with the ADMIN role. Finally, we add a line that starts with /** to match on every URL. The filter will stop once the URL matches on a URL, so make sure you put specific regular expressions toward the top and broad regular expressions toward the bottom to ensure you get the desired behavior. If you were working with Struts, you could either set up your struts in modules http://struts.apache.org/struts-core/userGuide/ configuration.html#5_4_1_Configure_the_ActionServlet_Instance or simply specify the StrutAction (e.g., /CustomerAdd.do) in the objectDefinitionSource.

At this point, we are done with applicationContext.xml file. To complete our demonstration application, all we need to do now is create a login form and put something in the /secured directory to see that our Acegi authentication and authorization configuration is working. (See the acegi-demo.zip for /acegilogin.jsp and /secured/index.jsp.)

The login form is very simple; it has input fields for the username and password, j_username and j_password, respectively, and a form action pointing to j_acegi_security_check since that is what the authenticationProcessingFilter filter listens for to capture every login form submission.

Test your configuration and inspect the Tomcat logs and the Log4J log file that we configured for this application if you run into problems.

Now That I'm Over the Initial Learning Curve, What's Next?
Once you have this simple Acegi demonstration application running, you will undoubtedly want to increase its sophistication. The first thing I would want to do is to add a JDBC profile in addition to the simple in-memory profile.

I can understand the excitement after getting the initial application up and running, but you still have some reading to do in order to eclipse the initial learning curve. Read through the articles posted in the External Web Articles section of the Acegi Web site http://acegisecurity.sourceforge.net. Read through the Reference Documentation provided by Ben Alex, the creator of Acegi. Ben does a good job of providing help through the support forum too. Also, read the well-kept JavaDocs as your main source of information once you get familiar with Acegi. Of course, you can opt to read the source code - it's open source!

Since this is your first time using Acegi, test after each change to the applicationContext.xml file. The process of "one change, then test" will help you understand exactly what change to the applicationContext.xml file caused an error if one should occur. If you make four changes to that file, restart the application and get an error, then you won't know which one of the four changes caused the error.

Note that I kept this application very simple. As you add in features such as Acegi's caching, you will need to add the appropriate libraries (or JARs). Look at the Acegi example application available on the Acegi Web site to get access to all the various libraries. The example application on the Acegi Web site is complex, so it is not the best place to start to get over the initial learning curve, unfortunately, hence my attempt to make it easier with the article!

No Groups in Acegi?
Acegi will let you work with the notion of groups. When you put a person in a group, you are just grouping the permissions (or roles) that the group does or does not have. So, when you set up your LDAP or JDBC profile, you need to make sure that the query returns the roles that the users' groups should have access to.

Conclusion
Acegi is a very configurable, open source security framework that will finally let you reuse and port your security layer components. It can be daunting at first, but this article should easily remove the stress in getting over the learning curve. Remember, you need to get this simple application running, test after each change, and read the recommended readings to fully surmount the initial learning curve. After you follow these steps, you will be well on your way to mastering Acegi.

5 comments:

vanusha said...

Thank you.
Very clear.

Steve said...

Up and running on first server start! Thanks for throwing this together. Now have some configuration to do... ;)

Yohan Liyanage said...

Good Article. Thank You.

Evans Anyokwu said...

Great article, you've done a great job by taking the time to write this amazing piece of tutorial. Keep it up!

Vinod said...

Thanks for the nice article. Do you think you can post a similar article for new Spring Security module in version 2.5.6 ?