Acegi Security in one hour
A concise guide to securing your Java Web applications
Acegi Security has been generating some serious positive buzz among Java enterprise developers, so you might be wondering how it works. In this article, ShriKant Vashishtha walks you through all the steps of a hands-on Acegi Security implementation. First you'll set up form-based authentication and authorization services for a Java-based Web application, then you'll customize Acegi Security for dynamic authorization, as well as integration with proprietary authentication implementations such as LDAP.
Acegi Security is a powerful and flexible security solution for Java enterprise applications built using the Spring framework. Spring-based dependency injection makes Acegi easy to configure and implement in a completely nonintrusive way. This is a boon to organizations that might not want to implement the Spring framework as a whole but still need effective, reusable security for legacy applications.
This article gives you a concise jump-start to implementing Acegi Security for a basic order-processing application. You'll set up authentication and authorization services for the application, and you'll implement those security features in form-based Web pages. After working through the example, you should be able to set up basic form-based security for any Web application in about an hour.
Following a quick introduction to the implementation example, you'll learn about some of the ways you can customize application security using Acegi. You'll see how to set up dynamic role-based authorization based on a database that maps user roles to URLs. Finally, you'll find out how to create a custom Acegi Security authentication implementation that can integrate with existing proprietary authentication implementations.
Environment setup
I wanted to demonstrate Acegi's applicability to a wide range of implementations, not just Spring-based applications. I built the example application using JEE 5, with JavaServer Pages for the presentation layer and SiteMesh for Web layout. The application could just as easily be built using Struts 2, and the Struts 2 infrastructure is already in place in the source code, though not implemented. I used Spring dependency injection to implement Acegi security for the application. See the Resources section to download the application source code. Follow these steps to set up the application environment:
Step 1. Download Acegi, Spring 2, and SiteMesh (see Resources for download links).
Step 2. Create the following folder structure in a Java project:
src - Contains Java source code
test - Contains test cases
config - Any property/XML configuration file that needs to be inside the classpath
web - Contains the Web application
|
decorators - Contains SiteMesh decorators
images - Contains images, if any
scripts - JavaScript files
styles - Cascading Style Sheets (CSS)
WEB-INF
|
jsp - Contains JavaServer Pages files (JSPs)
lib - Contains JARs
Step 3. Copy the following JAR files into the WEB-INF/lib directory:
acegi-security-1.0.5.jar - Main classes of the Acegi Security system
cglib-2.1.3.jar - Code-generation library used by Spring
commons-codec-1.3.jar - Encoders and decoders such as Base64, Hex, Phonetic, and URLs
commons-lang-2.1.jar - Helper utilities for java.lang APIs
ehcache-1.2.3.jar - Used for basic caching purposes
freemarker-2.3.8.jar - Used by the Struts implementation
jstl.jar, standard.jar - JavaServer Pages Standard Tag Library (JSTL) tag library
log4j-1.2.13.jar - For logging
ognl-2.6.11.jar - OGNL library used by the Struts implementation
sitemesh-2.3.jar - SiteMesh JAR
spring.jar - Spring Framework JAR
struts2-core-2.0.8.jar - Struts 2 core JAR
xwork-2.0.3.jar - Used by Struts
Changes to web.xml
Because Acegi Security is based on the concept of servlet filters and interceptors, you need to add entries for the FilterToBeanProxy filter to your application's web.xml deployment descriptor, as shown in Listing 1.
Listing 1. Adding servlet filters to web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="2.4">
<display-name>AcegiTraining</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
<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>/j_acegi_security_check</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/j_acegi_logout</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
...
</web-app>
FilterToBeanProxy requires an initialization parameter, targetClass. The targetClass parameter locates the first object of the specified class in the application context. In the configuration in Listing 1, that class is org.acegisecurity.util.FilterChainProxy. The related bean object in the application context is filterChainProxy, shown in Listing 2.
Listing 2. filterChainProxy
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
...
</value>
</property>
</bean>
Notice that Listing 1 defines multiple filter mappings for the Acegi filter. You could instead get away with using a more general filter mapping, as shown in Listing 3.
Listing 3. A general filter mapping
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
However, if you use the filter mapping in Listing 3, each and every URL is intercepted by the Acegi filter. And the filter now requests authorization details for static resources (JavaScript, CSS, HTML, and images) too, which you may not want to secure. You can avoid this trap by using specific URL patterns.
Order is essential when placing servlet filters. Because the example application uses filters for Acegi, JSP, and SiteMesh, you need to place the Acegi filter first, followed by the JSP and SiteMesh filters, respectively.
Adding authentication services
In a secured Web application, authentication is the process that verifies the user's identity. When a user clicks on a link, an HTTP request goes to the Web server and in turn to the application server that holds the requested resource. The server then checks if the resource corresponding to the link is protected or not. If the resource is protected and the user is not already authenticated, a security mechanism redirects to a login page. Based on the login credentials the user supplies on that page, the application either performs the next step or again redirects to a login page.
As part of authenticating the example application using Acegi Security, you need to configure Acegi beans in a Spring configuration file. Acegi Security requires many Spring beans, so you can put them in a separate configuration file named applicationContext-acegi-security.xml.
FilterChainProxy, you'll recall from Listing 1, intercepts all HTTP requests for specified URL patterns. It then delegates these requests to a series of Spring-managed beans (filters) defined with the filterInvocationDefinitionSource bean property for a specified URL pattern. Listing 4 shows the filter chain for Acegi Security.
Listing 4. Filter chain
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/j_acegi_security_check*=httpSessionContextIntegrationFilter,authenticationProcessingFilter
/**/*=httpSessionContextIntegrationFilter,logoutFilter,
authenticationProcessingFilter,securityContextHolderAwareRequestFilter,
anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
Acegi Security performs two steps when an unauthenticated user attempts to access a protected resource:
Step 1: Redirecting to login page
When any unauthenticated user tries to access a protected link, ExceptionTranslationFilter detects it as an exception and launches AuthenticationEntryPoint. AuthenticationEntryPoint redirects the HTTP request to a login page, as you can see from the authenticationProcessingFilterEntryPoint bean definition in Listing 5.
Listing 5. Bean definition for authenticationProcessingFilterEntryPoint
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<ref local="authenticationProcessingFilterEntryPoint" />
</property>
...
</bean>
<bean id="authenticationProcessingFilterEntryPoint"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl">
<value>/login.jsp</value>
</property>
<property name="forceHttps">
<value>false</value>
</bean>
Step 2: Submission of authentication credentials
The user now fills out the login form and submits the HTTP request. FilterChainProxy intercepts the request again. Because the request is submitted to the /j_acegi_security_check URL pattern, the request is delegated to the filter chain in Listing 4, and AuthenticationProcessingFilter performs the actual job of authentication. AuthenticationProcessingFilter, shown in Listing 6, checks the credential (user ID/password) information entered. If the authentication process is successful, the request is forwarded to the requested page (if the user is authorized to access it; more about this later). If authentication fails, the user is redirected to the login page again.
Listing 6. AuthenticationProcessingFilter
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="authenticationFailureUrl">
<value>/login.jsp?login_error=1</value>
</property>
<property name="defaultTargetUrl">
<value>/</value>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_security_check</value>
</property>
</bean>
authenticationManager, defined in Listing 7, is responsible for passing requests through a chain of AuthenticationProviders.
Listing 7. authenticationManager definition
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider" />
<ref local="anonymousAuthenticationProvider" />
</list>
</property>
</bean>
The job of the AuthenticationProvider is to check the validity of the Authentication request object. Listing 7 uses DaoAuthenticationProvider to check user credentials. User-credential details are captured with UserDetailsService (specified by the userDetailsService bean definition). The definition of AuthenticationProvider is shown in Listing 8. It uses InMemoryDaoImpl as the UserDetailsService implementation.
Listing 8. AuthenticationProvider definition
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"/><ref local="userDetailsService"/></property>
<property name="userCache">
...
</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>
Normally you'd write a custom UserDetailsService implementation that extracts the user information from a data store (such as a database or XML file). But often such a complex implementation isn't required, especially at the beginning of integrating Acegi security. You really don't want to spend much time writing database-based UserDetailsService implementations at this point. Instead, the InMemoryDaoImpl implementation comes in handy. In this case, you keep username, password, and role information inside users.properties.
Listing 9 shows a sample of users.properties. Note that the property's key is the username, and the property's value contains password and role information.
Listing 9. User information
james=tom@1231,ROLE_TECHNICIAN
krishna=krish2341,ROLE_TECHNICIAN
smith=pravah@001,ROLE_ADMIN
Keep in mind that you can use any implementation of UserDetailsService -- including JdbcDaoImpl, which is readily provided by the Acegi Security framework -- to validate user credentials. Listing 10 shows a modified definition of the userDetailsService Spring bean using JdbcDaoImpl.
Listing 10. Modified userDetailsService definition
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"/><ref local="jdbcDaoImpl"/></property>
<property name="userCache">
...
</property>
</bean>
<bean id="jdbcDaoImpl"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
You've now completed the authentication phase of the Acegi Security implementation. The authentication process establishes the user's identity in the application. The system still need to know if the user is authorized to view the page requested, however. This is the job of authorization.
Adding authorization services
Authorization in Acegi Security is performed mainly by the FilterSecurityInterceptor filter. This filter identifies a user-role relationship for a URL. The URL patterns and their associated roles are defined with the objectDefinitionSource property. Any user can access the URLs belonging to the ROLE_ANONYMOUS role. For other URLs you define different roles. Listing 11 shows the sample application's authorization configuration.
Listing 11. Authorization configuration
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="accessDecisionManager">
<ref local="httpRequestAccessDecisionManager" />
</property>
<property name="objectDefinitionSource">
<value>
PATTERN_TYPE_APACHE_ANT
/index.jsp=ROLE_ADMIN,ROLE_TECHNICIAN
/order/createOrder.jsp=ROLE_TECHNICIAN
/order/authorizeOrder.jsp=ROLE_ADMIN
/login.jsp=ROLE_ANONYMOUS,ROLE_TECHNICIAN,ROLE_ADMIN
</value>
</property>
</bean>
In Listing 11, a user with the ROLE_TECHNICIAN role can access the "Create Order" Web page, but not the "Authorize Order" Web page. The reverse is true for a user with the ROLE_ADMIN role.
If a user doesn't have authorization privileges for a secured resource, the AbstractSecurityInterceptor (base class of FilterSecurityInterceptor) throws an exception, which ExceptionTranslationFilter then captures. AccessDeniedHandlerImpl, a collaborator of exceptionTranslationFilter (shown in Listing 12), then forwards the request to an "Access Denied" error page.
Listing 12. exceptionTranslationFilter
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<ref local="authenticationProcessingFilterEntryPoint" />
</property>
<property name="accessDeniedHandler">
<bean
class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp" />
</bean>
</property>
</bean>
FilterSecurityInterceptor calls AccessDecisionManager to make access-control decisions. AccessDecisionManager polls a series of AccessDecisionVoter implementations for making authorization decisions. The example implementation uses RoleVoter as the concrete implementation of AccessDecisionVoter.
RoleVoter votes if ConfigAttribute begins with ROLE_. As you can see in Listing 11, all roles begin with ROLE_ and so can be voted on by RoleVoter. If you have different naming conventions for authorization roles, you can implement a new AccessDecisionVoter. If RoleVoter doesn't find an exact match to any ConfigAttribute starting with ROLE_, it votes to deny access. If no ConfigAttribute begins with ROLE_, the voter abstains from voting. Listing 13 shows the bean definition of httpRequestAccessDecisionManager (referenced in Listing 11's accessDecisionManager property) and its relationship with a series of AccessDecisionVoter implementations.
Listing 13. httpRequestAccessDecisionManager
<bean id="httpRequestAccessDecisionManager"
class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions">
<value>false</value>
</property>
<property name="decisionVoters">
<list>
<ref bean="roleVoter" />
</list>
</property>
</bean>
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter" />
Logout functionality
Logout functionality is performed by LogoutFilter. You use a constructor argument to provide the URL that LogoutFilter redirects to after successful logout. Another constructor argument specifies a list of handlers that execute as a part of logout functionality. Listing 14 specifies SecurityContextLogoutHandler, which invalidates the httpSession. You can add more handlers to perform extra functionality if required.
Listing 14. logoutFilter definition
<bean id="logoutFilter"
class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/index.jsp" />
<!-- URL redirected to after logout -->
<constructor-arg>
<list>
<bean
class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
</bean>
Et voilĂ ! You've completed the environment and system configuration required to implement Acegi Security for your application. All that remains is to code the application's Web pages.
Coding the Web pages
Now, in five steps, you'll do some JSP coding to implement form-based authentication in the Web application:
Step 1. Create the login page, using the contents of Listing 15.
Listing 15. Login JSP
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<script>
function focusUserName(){
document.forms[0].j_username.focus();
}
</script>
<title>Login</title>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<link href="${ctx}/styles/global.css" type="text/css" rel="stylesheet"/>
<link href="${ctx}/images/favicon.ico" rel="SHORTCUT ICON"/>
</head>
<body onload="focusUserName()">
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<div id="container">
<div id="intro">
<div id="image">
</div>
<div id="gap">
</div>
<br><br><br><br><br>
<form action="<c:url value='j_acegi_security_check'/>" method="POST">
<table width=50% border=0 align="center">
<c:if test="${not empty param.login_error}">
<tr><td colspan=2><font color="red">
Your login attempt was not successful, try again.<BR>
Reason: <%= ((AuthenticationException) session.getAttribute(AbstractProcessingFilter
.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>
</font></td></td>
</c:if>
<tr><td>User:</td>
<td>
<input type='text' name='j_username'
<c:if test="${not empty param.login_error}">
value='<c:out value="${ACEGI_SECURITY_LAST_USERNAME}"/>'
</c:if>>
</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>Don't ask for my password for two weeks</td></tr>
<tr><td colspan='2'><input name="submit" type="submit">
<input name="reset" type="reset" value="Clear"></td></tr>
<!--tr><td colspan='2'></td></tr-->
</table>
</div>
</div>
</form>
</body>
</html>
The login form is submitted to j_acegi_security_check for authentication.
Step 2. If authentication is successful, the request is forwarded to the application's home page (index.jsp).
Listing 16. index.jsp
<html>
<body>
<p><a href="secure/createOrder.jsp">Create Order</a>
<p><a href="secure/authoriseOrder.jsp">Authorise Order</a>
</body>
</html>
Step 3. If the user doesn't have access to certain page, the accessDenied.jsp page, shown in Listing 17, is displayed.
Listing 17. accessDenied.jsp
<html>
<body>
<h1>Access Denied.</h1>
</body>
</html>
Step 4. Users with the ROLE_TECHNICIAN role can create orders via createOrder.jsp, shown in Listing 18. This functionality is not available for users with the ROLE_ADMIN role.
Listing 18. order/createOrder.jsp
<%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
<html>
<head/>
<body>
<h1>Welcome: <%= SecurityContextHolder.getContext().getAuthentication().getName() %></h1>
<p><a href="/">Home</a>
<p><a href="/j_acegi_logout">Logout</a>
<Create Order screen content goes here>
...
...
</body>
</html>
Step 5. Conversely, users with the ROLE_ADMIN role can authorize the created order, using authorizeOrder.jsp (shown in Listing 19). This functionality isn't available for users with the ROLE_TECHNICIAN role.
Listing 19. order/authorizeOrder.jsp
<html>
<head/>
<body>
<%@ 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>
<Authorise Order screen content goes here>
...
...
</body>
</html>
You can test the configuration by logging in first as james and then as smith. Because james has the ROLE_TECHNICIAN role, he can access createOrder.jsp. But when he tries to access authorizeOrder.jsp, he gets the accessDenied.jsp page. On the other hand, smith can access authorizeOrder.jsp but can't access createOrder.jsp, because she belongs to the ROLE_ADMIN role.
Customizing Acegi Security for dynamic authorization
The implementation example you've just completed is based on the assumption that authorization details won't change in the application's lifetime. Accordingly, in the filterInvocationInterceptor bean definition in Listing 11, the objectDefinitionSource property contains the actual URL-to-role mapping. But for some applications, authorization access needs to be dynamic. For instance, one particular URL might start out being accessible only to a certain role. But over time, business requirements might dictate that you change or add roles against that URL. For those cases, it's better to have URL-to-role mapping in a database instead of a static configuration file.
To make authorization details dynamic, the first task is to change the implementation of the objectDefinitionSource property to get URL-to-role mapping from a database. FilterSecurityInterceptor uses an ObjectDefinitionSource implementation specific for Web applications. (It implements FilterInvocationDefinitionSource interface, which is derived from the ObjectDefinitionSource interface.) To implement a new custom FilterInvocationDefinitionSource, you need to implement its three methods.
In the Acegi Security framework, you already have a class named AbstractFilterInvocationDefinitionSource, which implements two methods but adds a new abstract method. So you are left with implementing two methods (getConfigAttributeDefinitions() and lookupAttributes()) in total in your custom FilterInvocationDefinitionSource implementation. Because the getConfigAttributeDefinitions() method is used by AbstractSecurityInterceptor to help with initial configuration checking, you can return null from this method. The only method left to implement is lookupAttributes(). Listing 20 shows a sample implementation of the lookupAttributes() method.
Listing 20. Implementation of the lookupAttributes() method
public ConfigAttributeDefinition lookupAttributes(String url) {
// Strip anything after a question mark symbol, as per SEC-161. See also
// SEC-321
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
SecureResource secureObject = authorizationService.getSecureObject(url);
if (secureObject == null)// if secure object not exist in database
return null;
// retrieving roles associated with this secure object
List<Role> secureObjectRoles = authorizationService
.getSecureObjectRoles(secureObject);
// creating ConfigAttributeDefinition
if (secureObjectRoles != null && !secureObjectRoles.isEmpty()) {
ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
StringBuffer rolesStr = new StringBuffer();
for (int i = 0; i < secureObjectRoles.size(); i++) {
Role sor = (Role) secureObjectRoles.get(i);
rolesStr.append(sor.getName()).append(",");
}
configAttrEditor.setAsText(rolesStr.toString().substring(0,
rolesStr.length() - 1));
ConfigAttributeDefinition configAttrDef = (ConfigAttributeDefinition) configAttrEditor
.getValue();
return configAttrDef;
}
return null;
}
In Listing 20, you use AuthorizationService to get the mappings between a secured resource and its associated roles. This sample uses AuthorizationServiceImpl to interact with database layer. For the sake of simplicity, the sample application uses the in in-memory data store instead of a database for the data-access object (DAO) implementation. You can change its implementation using Java Database Connectivity (JDBC), the Java Persistence API (JPA), or any other persistence mechanism you like.
Now that you're done implementing the custom FilterInvocationDefinitionSource, you need to configure it in the Acegi configuration. Listing 21 shows the modified filterInvocationInterceptor bean definition.
Listing 21. Modified filterInvocationInterceptor bean definition
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="accessDecisionManager">
<ref local="httpRequestAccessDecisionManager" />
</property>
<property name="objectDefinitionSource">
<ref local="dbdrivenFilterInvocationDefinitionSource" />
</property>
</bean>
<bean id="dbdrivenFilterInvocationDefinitionSource"
class="com.abc.security.authorization.DatabaseDrivenFilterInvocationDefinitionSource">
<property name="authorizationService">
<ref local="authorizationService" />
</property>
</bean>
<bean id="authorizationService"
class="com.abc.security.authorization.service.AuthorizationServiceImpl">
<property name="authDAO">
<ref local="authDAO" />
</property>
</bean>
<bean id="authDAO"
class="com.abc.security.authorization.dao.AuthorizationDAOImpl" />
Now this interceptor will hit the database for all authorization information. You might want to cache this information to minimize the performance impact of repeated hits to the database. You can cache it in the DAO layer using features in object-relational mapping (ORM) persistence implementations such as Hibernate, or you can cache it on the service layer.
Custom authentication integration with Acegi Security
Another customization scenario involves integrating Acegi Security with an existing proprietary authentication implementation. For instance, the hypothetical XYZ enterprise is already using a proprietary LDAP (Lightweight Directory Access Protocol)-based authentication solution. Because the enterprise uses this authentication mechanism across all Java EE applications, its IT team might not want to use an altogether new authentication mechanism for a new application. In a case like this, it would be nice if the existing authentication implementation could be integrated with Acegi, which is doable.
To implement authentication functionality, you need to create a new AuthenticationProvider and implement its authenticate() method. There you can pass the user credentials to the external service or component that authenticates the user. Listing 22 shows the AuthenticationProvider implementation.
Listing 22. Custom AuthenticationProvider implementation
public class XYZAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
...
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
XYZSecurityService eiService = new ABCSecurityServiceImpl();
LDAPResponse ldap = null;
try {
ldap = eiService.authenticateUser(authentication.getPrincipal()
.toString(), authentication.getCredentials().toString());
} catch (AppException e) {
throw new BadCredentialsException(
"Exception occurred while executing security service", e);
}
...
...
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
user, authentication.getCredentials(), user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
}
The authenticate() method in Listing 22 calls an existing authentication service -- XYZSecurityService -- that authenticates user credentials. You need to modify the bean definition in Listing 8 to accommodate the custom AuthenticationProvider implementation. Simply replace the second line with:
<bean id="daoAuthenticationProvider" class="com.xyz.security.XYZAuthenticationProvider">
No comments:
Post a Comment