Thursday, December 28, 2006

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.

22 comments:

  1. 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!

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

    ReplyDelete
  3. 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..

    ReplyDelete
  4. 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!

    ReplyDelete
  5. 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?

    ReplyDelete
  6. 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' />"

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

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

    ReplyDelete
  9. thanks, great article *thumbs up*

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

    ReplyDelete
  10. 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

    ReplyDelete
  11. 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?

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

    Thanks

    Arivuvel Ramu

    ReplyDelete
  13. 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?

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

    ReplyDelete
  15. 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

    ReplyDelete
  16. vikas,

    I had this problem too the solution was to explode the spring security demo war and to add log4j to the classpath for tomcat in lib or common/lib for pre Tomcat 6.

    Paul Sundling
    http://www.tkz.net

    ReplyDelete
  17. Hi,

    Abhi can u suggest!!!

    This is regarding the logout through Acegi. This is my problem:

    1: I have an URL (http://tpms-dev.frt.com/tpms/) --> clicked this URL a dialogue box appears where user enters the userid and password for authentication and clicks OK and this leads to my main page.

    2: Now if I click Logout button I want that it should redirect to the URL ( mentioned in point 1 above) and the same dialogue box should appear for the user to re-enter the login credentials.
    But instead of that it redirects to my main page again.

    ReplyDelete
  18. Hi Abhi ,
    thanks for the good reference

    can i have this sample to download..

    Thanks in advance
    Ashok

    ReplyDelete
  19. Hi

    I am Visha....
    Thanks for yr help...it really helped me to figure out my problem

    ReplyDelete
  20. hello it's very good article to understand the framework Acegi with Spring and got almost the same problem as mentioned by others when I try to connect the first time to the page(1) seeking to pass through I authentication redirects to the login page which is well within the safe side so I did authenticate but when I disconnect I can connect to this page (1) without using authentication via the url or the button at the top of previous page
    Pending your answers and thank you

    ReplyDelete
  21. Hi Abhi,

    Nice Article. I have a question please anyone comment on this...

    I have a URL query string http://localhost:8080/DemoApp/admin.htm?action=display

    In filterInvocationInterceptor bean, how can i add this "/admin.htm?action=display" under the PATTERN_TYPE_APACHE_ANT.

    Thanks,
    lps

    ReplyDelete

Popular Posts