• Home
  • RSS Feed
  • Log in

How to get started with the Acegi framework
Posted by Okke Harsta in the early morning: March 4th, 2007

How to get started with the Acegi framework and implement your own Security provider?

In the old days folks used the J2EE securing capabilities of the app server. This is of course still an option, but there are superior alternatives like the Acegi framework. Acegi is far from new and with the latest releases it has become a very stable and easy-to-use framework, especially when combined with Spring. I had to implement a custom security provider for a customer and was very surprised how easy this was accomplished. This blog describes the steps I took to get started with Acegi.

The starting point is a -very- simple web application using the Spring MVC framework. This is the web.xml:


<web-app 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 web-app_2_4.xsd">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/acegi-security.xml</param-value>
    </context-param>

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

    <servlet>
        <servlet-name>blog-acegi-basic</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>blog-acegi-basic</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
</web-app>

And the blog-acegi-basic-servlet.xml configuration.


<?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="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass">
            <value>org.springframework.web.servlet.view.JstlView</value>
        </property>
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <bean name="/site/admin.html"
        class="com.xebia.mvc.AdminController">
    </bean>

    <bean name="/site/public.html"
        class="com.xebia.mvc.PublicController">
    </bean>

</beans>

The two Controllers are also very simple (for demo purposes):


public class AdminController implements Controller {
	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	public ModelAndView handleRequest(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		return new ModelAndView("admin", "info", "adminInfo");
	}
}

public class PublicController implements Controller {
	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	public ModelAndView handleRequest(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		return new ModelAndView("public", "info", "publicInfo");
	}
}

As are the views admin.jsp


<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="authz" uri="http://acegisecurity.org/authz" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
		<title>Admin</title>
	</head>
	<body>
		Hello Admin
		<c:out value='${info}' />
	</body>
</html>

and public.jsp that are placed in the folder WEB-INF/views:


<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="authz" uri="http://acegisecurity.org/authz" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
		<title>Public</title>
	</head>
	<body>
		Hello Public
		<c:out value='${info}'/>
	</body>
</html>

For now we'll leave the acegi-security.xml empty.


<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
</beans>

When you use maven2 to build your web-application you can use the jetty plugin to test it. The following pom.xml has all dependencies and the plugin configuration to use jetty with the jdk1.5:


<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xebia</groupId>
    <artifactId>acegi-blog</artifactId>
    <packaging>war</packaging>
    <version>0-1-SNAPSHOT</version>
    <name>acegi-blog</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-mock</artifactId>
            <version>2.0.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.acegisecurity</groupId>
            <artifactId>acegi-security</artifactId>
            <version>1.0.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-jdbc</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aop</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-webmvc</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-dao</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-support</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-remoting</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.1</version>
                <configuration>
                    <scanIntervalSeconds>1</scanIntervalSeconds>
                    <contextPath>/blog-acegi</contextPath>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

The transitive dependency mechanism of maven2 pulls in a lot of the Spring dependencies because of the Acegi 1.0.3 pom configuration. Because I want to use the latest release of Spring I have excluded them from Acegi. When you fire up jetty:

$ mvn jetty:start

You can view the public and admin page with the url's http://localhost:8080/blog-acegi/site/public.html and http://localhost:8080/blog-acegi/site/admin.html. Now we will introduce Acegi into the equation. We will secure the admin page with an user/password combination, while the public page may remain unsecured. First we add an Acegi Filter to the web.xml:


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

The FilterToBeanProxy is configured with a targetClass. Acegi expects an instance of this target class configured in the Spring bean context. We will code the Acegi beans in the Spring context file acegi-security.xml.

As specified in the Acegi Filter we need at least an instance of the FilterChainProxy in the acegi-secutity.xml.


<!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>
				<![CDATA[
                PATTERN_TYPE_APACHE_ANT
                /**=exceptionTranslationFilter,filterInvocationInterceptor,authenticationProcessingFilter
            ]]>
			</value>
		</property>
	</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>

	</bean>

	<bean id="authenticationProcessingFilter"
		class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager"
			ref="authenticationManager" />
		<property name="authenticationFailureUrl"
			value="/login.jsp?login_error=1" />
		<property name="defaultTargetUrl" value="/site/public.html" />
		<property name="filterProcessesUrl"
			value="/j_acegi_security_check" />
	</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>
				<![CDATA[
                PATTERN_TYPE_APACHE_ANT
                /site/admin.html=ROLE_ADMIN
            ]]>
			</value>
		</property>
	</bean>

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

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

    <bean id="userDetailsServiceImpl"
        class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
        <property name="userProperties">
            <props>
                <prop key="admin">secret,ROLE_ADMIN</prop>
            </props>
        </property>
    </bean>

</beans>

This rather large xml document configures Acegi to prevent people to see the admin part of the application. The configured beans with some details:

  • The filterChainProxy defines all filters that will be called when an user requests a url that matches the Acegi Filter Chain Proxy filter mapping in the web.xml. I have configured 3 filters.
  • The exceptionTranslationFilter is responsible for redirecting to the login.jsp in case of a Security Exception. Without this bean our admin page would stil be secured, but the user would get a error stacktrace when trying to access the page without being logged in.
  • The authenticationProcessingFilter is responsible for handling the form post in the login.jsp. In case of failure it will redirect to the login page and in case of a successfull login the user will be redirected to the defaultTargetUrl.
  • The filterInvocationInterceptor contains the actual configuration of the security restrictions. It uses two acegi decisionVoters that will check the username/password and the role of the user. In this case we limit the access to the url /site/admin.html to users with the role ROLE_ADMIN.
  • The authenticationManager is the hook for all authenticationProviders we want to configure. In this example we use only one provider, but you could configure as many as you would like. I have not encountered an usecase yet where this was required.
  • The authenticationProvider we use is the one which is provided by Acegi. The retrieving of the user is being delegated to an implementation of the userDetailsService
  • The implementation of the userDetailsService we use is the InMemoryDaoImpl which can be configured with a list of users, password and roles. Acegi also provides a JdbcDaoImpl which actually retrieves the users and their roles from the database.

Because auto-formatting the acegi-secutiry.xml file can mess up the ANT like configuration I have coded those parts within CDATA sections. The one thing missing is the login.jsp; the login.jsp needs to be placed in the root of war file. When using maven2 for packaging the war the login.jsp must be placed in the webapp directory.


<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter"%>
<%@ page
	import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"%>
<%@ page import="org.acegisecurity.AuthenticationException"%>

<html>
	<head>
		<title>Login</title>
	</head>

	<body>
	<c:if test="${not empty param.login_error}">
		<font color="red"> Your login attempt was not successful, try
		again.<BR>
		<BR>
		Reason: <%=((AuthenticationException) session
											.getAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY))
											.getMessage()%> </font>
	</c:if>

	<form action="<c:url value='j_acegi_security_check'/>" method="POST">

	<center>
		<table align="center" cellpadding="4" cellspacing="0" border="0"
					class="loginform">
			<tr>
				<td bgcolor="f0f0f0" colspan="2">Enter your details below to
					login to admin site:</td>
			</tr>
			<tr />
			<tr>
				<td nowrap align="right" valign="top"><label class="label"><u>U</u>sername:</label></td>
				<td><input type='text' name='j_username' accessKey="U"></td>
			</tr>
			<tr>
				<td nowrap align="right" valign="top"><label class="label"><u>P</u>assword:</label></td>
				<td><input type='password' name='j_password' accessKey="P"></td>
			</tr>

			<tr>
				<td valign="middle" align="center" colspan="2"><input
						id="loginButton" type="submit" value="Log In" /></td>
			</tr>

		</table>
	</center>
	</form>

	</body>
</html>

Again using the jetty plugin we can verify the result. When trying to access the admin page we will be prompted for an username and password and when providing the correct combination we will be forwarded to the admin page. The public page is still accessible for everyone. I have attached the source code of this example. The InMemoryDaoImpl is of course not very sophisticated. In a next blog I'll show you how to implement your own security provider.

  • Share/Bookmark

Filed under Security | 3 Comments »



3 Responses to “How to get started with the Acegi framework”



    Dali Says:
    Posted at: March 15, 2007 at 4:07 pm

    hello okkke
    I did not understand a thing in your example: if I directly launch the admin.jsp page how what my filter is will know that “/site/admin.html” is related to “admin.jsp” in the property objectDefinitionSource:
    /site/admin.html =ROLE_ADMIN.

    I will be reconnaisant if you answer me
    thiks



    Okke Harsta Says:
    Posted at: March 15, 2007 at 10:15 pm

    Dali,

    You can not access the admin.jsp page directly. You can only request it through the servlet mapping url configured in the blog-acegi-basic-servlet.xml; being /site/admin.html. The InternalResourceViewResolver links the url to a bean instance and the url is secured using the acegi interceptor.



    Ania Says:
    Posted at: August 1, 2007 at 2:52 pm

    Thank you for the tutorial, it helped me way.

    Just a little remark for those as new to webapps as I am:

    once you change the location of your context config like this:

    contextConfigLocation
    /WEB-INF/acegi-security.xml

    your applicationContext.xml no longer gets loaded. So watch out blindly following the steps of the tutorial if you already have Spring configured.



Leave a Reply

Click here to cancel reply.

Deployment automation for Java application running on Websphere, WebLogic and JBoss

Archives

  • March 2010
  • February 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009

Training

  • Enterprise Java Security Applied
    Custom made, in-company

Xebia Sites

  • Xebia Corporate
  • Xebia France
  • Xebia India

Categories

  • Java (282)
  • Agile (109)
  • General (50)
  • Testing (42)
  • Performance (42)
  • Hibernate (36)
  • Scrum (33)
  • Podcast (31)
  • Architecture (31)
  • Spring (28)
  • SOA (24)
  • Maven (22)
  • Project Management (22)
  • Middleware (23)
    • Deployment (14)
  • Flex (17)
  • JPA (17)
  • Eclipse (15)
  • Xebia Labs (15)
  • Quality Assurance (14)

Tag Cloud

    JavaOne product owner Ajax Java Xebia Architecture Spring fitnesse IntelliJ Agile Awareness Workshop Introduction to Agile Scala Hibernate Poppendieck Scrum Seam Agile qcon Groovy Testing XML Performance Lean Functional Programming Maven Semantic Web SOA Grails Closures esb