Spring Security with Acegi Security
Acegi Security provides a comprehensive security solution for J2EE-based enterprise software applications, built using the Spring Framework. Acegi provides authentication service through the use of a web filter, an AuthenticationProvider and an AuthenticationEntryPoint. Authorization in Acegi can be done at different levels: authorizing web requests, authorizing which methods can be invoked, and authorizing access to individual domain object instances. In this example I will describe how to implement form-based authentication and URL level authorization using Acegi.
For this example, I use two secure JSPs, the secure/authenticatedusers.jsp file is available to all the authenticated users, and secure/admin/admin.jsp is only available to the users with ADMIN role. The login.jsp is used for login and denied.jsp is used to redirect unauthorized users. Follow these steps to run the example:
  1. Download Acegi and Spring.
  2. Start with a dynamic web project in eclipse.
  3. Copy the following Jar files into the WEB-INF/lib directory.
    acegi-security-1.0.3.jarMain classes of the Acegi security system.
    commons-codec-1.3.jarEncoders and decoders such as Base64, Hex, Phonetic and URLs.
    ehcache-1.2.3.jarUsed by the authentication provider.
    jstl.jar, standard.jarThe JSTL tag library
    spring.jarSpring framework
    commons-logging.jar, cglib-nodep-2.1_3.jarAvailable in the spring download (spring with dependencies)
  4. Create the Login page: The following is a listing for the login page.
    <jsp:root version="1.2" xmlns:jsp="http://java.sun.com/JSP/Page"
    xmlns:c="urn:jsptld:http://java.sun.com/jsp/jstl/core">
    <jsp:directive.page contentType="text/html; charset=UTF-8" />
    <jsp:directive.page
    import="org.acegisecurity.ui.AbstractProcessingFilter, org.acegisecurity.ui.webapp.AuthenticationProcessingFilter, org.acegisecurity.AuthenticationException" />
    <head>
    <title>Login</title>
    </head>
    <body>
    <form action="j_acegi_security_check" method="POST">
    <table>
    <tr>
    <td>User:</td>
    </td><input type='text' name='j_username' />
    </td>
    </tr>
    <tr>
    <td>Password:</td>
    <td><input type='password' name='j_password' /></td>
    </tr>
    <tr>
    <td><input type="checkbox" name="_acegi_security_remember_me" /></td>
    <td>Remember me (14 days)</td>
    </tr>
    <tr><td colspan='2'><input name="submit" type="submit" /></td></tr>
    <tr><td colspan='2'><input name="reset" type="reset" /></td></tr>
    </table>
    </form>
    </body>
    </jsp:root>
    login.jsp

    Notes:
    • The login form uses j_acegi_security_check instead of j_security_check normally used for form-based authentication. Similarly for logout, we use j_acegi_logout action.
    • In order to remember a user for a period of time, use the _acegi_security_remember_me attribute. This is configured in the applicatonContext.xml file(see below).
  5. Create the other JSP files: The listings for the JSP pages used for this example are shown below, along with their respective directories.
    <html>
    <body>
    Everyone
    <p><a href="secure/authenticatedusers.jsp">Authenticated users only</a>
    <p><a href="secure/admin/admin.jsp">Admins only</a>
    </body>
    </html>
    index.jsp
    Access Denied.
    denied.jsp
    <%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
    <h1>Welcome: <%= SecurityContextHolder.getContext().getAuthentication().getName() %></h1>
    <p><a href="../">Home</a>
    <p><a href="../j_acegi_logout">Logout</a>
    secure/authenticatedusers.jsp
    <%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
    <h1>Welcome: <%= SecurityContextHolder.getContext().getAuthentication().getName() %> is an Admin</h1>
    <p><a href="../../">Home</a>
    <p><a href="../../j_acegi_logout">Logout</a>
    </body>
    </html>
    secure/admin/admin.jsp
  6. The users.properties file: This example uses an in memory user registry and the WEB-INF/users.properties file is used to store the usernames and passwords.
    scott=tiger,ROLE_USER
    harry=potter,ROLE_ADMIN
    frodo=baggins,ROLE_USER
    WEB-INF/users.properties
  7. Configure the Acegi filter in the Web deployment descriptor: The web.xml file is shown below
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>SpringSecurity</display-name>

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

    <listener>
    <listener-class>
    org.springframework.web.context.ContextLoaderListener
    </listener-class>
    </listener>

    <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    </web-app>
    web.xml
  8. Configure Acegi in applicationContext.xml file: The following is a listing of the WEB-INF/applicationContext.xml file, followed be explanations of the different beans defined in there.
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans>

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

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

    <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
    <constructor-arg value="/index.jsp"/>
    <constructor-arg>
    <list>
    <ref bean="rememberMeServices"/>
    <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
    </list>
    </constructor-arg>
    </bean>

    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationFailureUrl" value="/login.jsp?errorId=1"/>
    <property name="defaultTargetUrl" value="/"/>
    <property name="filterProcessesUrl" value="/j_acegi_security_check"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

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

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

    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager">
    <bean 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>
    </property>
    <property name="objectDefinitionSource">
    <value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /secure/admin/**=ROLE_ADMIN
    /secure/**=IS_AUTHENTICATED_REMEMBERED
    /**=IS_AUTHENTICATED_ANONYMOUSLY
    </value>
    </property>
    </bean>

    <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="tokenValiditySeconds" value="1800"></property>
    <property name="key" value="changeThis"/>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
    <property name="providers">
    <list>
    <ref local="daoAuthenticationProvider"/>
    <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
    <property name="key" value="changeThis"/>
    </bean>
    <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
    <property name="key" value="changeThis"/>
    </bean>
    </list>
    </property>
    </bean>

    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="userCache">
    <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
    <property name="cache">
    <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
    <property name="cacheManager">
    <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
    </property>
    <property name="cacheName" value="userCache"/>
    </bean>
    </property>
    </bean>
    </property>
    </bean>

    <bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
    <property name="userProperties">
    <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="/WEB-INF/users.properties"/>
    </bean>
    </property>
    </bean>

    <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

    </beans>
    WEB-INF/applicationContext.xml
    1. FilterChainProxy: Delegates Filter requests to a list of Spring-managed beans.
    2. HttpSessionContextIntegrationFilter: Populates the SecurityContextHolder with information obtained from the HttpSession.
    3. LogoutFilter: Logs a principal out.
    4. AuthenticationProcessingFilter: Processes an authentication form.
    5. SecurityContextHolderAwareRequestFilter: A Filter which populates the ServletRequest with a new request wrapper.
    6. RememberMeProcessingFilter: Detects if there is no Authentication object in the SecurityContext, and populates it with a remember-me authentication token if a RememberMeServices implementation so requests.
    7. AnonymousProcessingFilter: Detects if there is no Authentication object in the SecurityContextHolder, and populates it with one if needed.
    8. AnonymousProcessingFilter: Detects if there is no Authentication object in the SecurityContextHolder, and populates it with one if needed.
    9. ExceptionTranslationFilter: Handles any AccessDeniedException and AuthenticationException thrown within the filter chain.
    10. AuthenticationProcessingFilterEntryPoint: Used by the SecurityEnforcementFilter to commence authentication via the AuthenticationProcessingFilter. This object holds the location of the login form, relative to the web app context path, and is used to commence a redirect to that form.
    11. AccessDeniedHandlerImpl: Used by ExceptionTranslationFilter to handle an AccessDeniedException.
    12. FilterSecurityInterceptor: Performs security handling of HTTP resources via a filter implementation.
    13. TokenBasedRememberMeServices: Identifies previously remembered users by a Base-64 encoded cookie.
    14. DaoAuthenticationProvider: An AuthenticationProvider implementation that retrieves user details from an UserDetailsService.
    15. InMemoryDaoImpl: Retrieves user details from an in-memory list created by the bean context.

15 comments:

lex said...

great article! was having a hard time getting a prototype to work, but with your instructions i was up and running in no time. thanks!

Shlok said...

Very informative and detailed article. Was very helpful, thanks for sharing...

Anonymous said...

not a good article..number of errors..looks like acopy of article from Acegi itself...

Some suggestion:-

provide the war file

Good Thing :-

Only the steps thats it..

drhilarius said...

Great and comprehensive introductory article! Provides a very efficient path to implementing basic Acegi security w/o getting too much into the details of the framework itself, although explaining the necessary dependencies between the major concepts. Again - good work!

seven said...

here i got an error when i tried to log in with scott whose pwd was supposed to be tiger. the error msg was "The requested resource (/AcgSpring/j_acegi_security_check) is not available."

can't figure out what's happening there. do you have any idea?

Sean said...

Thanks for the example!

I noticed a small typo in your 'login.jsp' HTML. you have an extra slash in your open TD tag.

"</td><input type='text' name='j_username' />"

should be

"<td><input type='text' name='j_username' />"

Tonelli said...

I could not make it work , I get this Error:

java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory

maybe someone can help me...

thanks and great article...

Abhi said...

tonelli,
you will have to include commons-logging.jar file in your classpath, it is available in the spring-with-dependencies download.

Anonymous said...

thanks, great article *thumbs up*

the same would be nice for basic authentication with spring security...

Anonymous said...

hey Abhi
Great article man!

But I am trying to use form-based authentication WITHOUT acegi security


After user enters its log name and password I get error:
The requested resource (/someapp/j_security_check) is not available.

What do you think coud cause this error ?

Thanks for your help

Anonymous said...

great. explained the most in the least. but after doing everything right, i.e. all libraries, code etc. in jboss4.2.2, i get this error: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '' is defined
it's bugging me. any help or is it just a matter of using tomcat?

Arivuvel Ramu said...

HI, I found the detailed configuration file and usage details.

Thanks

Arivuvel Ramu

Arivuvel Ramu said...

But still i am facing one more issues.

First time i have logged in with "X" Name and visited "XYZ.jsp" page. then i logged out.

Second time i have logged in with "Y" Name and visited "YXSD.jsp" page, then i try to call "XYZ.jsp" from the browser, still the "X" person session is not experied.

My Question: if i press /j_acegi_logout and my session also needs to be invalidated.

Could u plz help me?

je said...

Can you check the web.xml to make sure the /j_acegi_logout is accessible and not mapped to other resource?

vikas said...

hi,

when i put this in my web.xml
{code}

code from context-param -end contenxt-param
and filter
name mapping and listener class

{code}

my application won't run in apache
response is
Application at context path /loyalty_final could not be started
plz tell me how to solve this error