This series of articles is an introduction to using Acegi Security System to secure your Java enterprise applications. In the first article in this series, I introduced Acegi and explained how to use security filters to implement a simple, URL-based security system. In this second article, I begin to discuss the more advanced uses of Acegi, starting with writing an access control policy and storing it in ApacheDS, an open source LDAP directory server. I also show you how to configure Acegi to interact with the directory server to implement your access control policy. At the conclusion of the article, I present an example application that uses ApacheDS and Acegi to implement a secure access control policy.
Implementing an access control policy usually consists of two steps:
- Storing data about users and their roles in a directory server.
- Writing the security code that defines who can access and use the data.
Acegi relieves you from writing code, so in this article, I show you how to first store user and role information in ApacheDS and then implement an access control policy for that information. In the next article in this series, I will show you how to configure Acegi to secure access to your Java classes.
You can download the sample application at any point in the discussion. See Resources to download Acegi, Tomcat, and ApacheDS, which you need to run the sample code and the example application.
Lightweight Directory Access Protocol (LDAP) is probably the most popular protocol defining data formats for common directory operations such as reading, editing, searching, and deleting information stored in a directory server. This section briefly explains why a directory server is preferable to a properties file for storing security information and shows you how to structure and host your user information in an LDAP directory.
In the first part of this series, you learned a simple way to store your user information in the form of a properties file (see Part 1, Listing 6). The properties file stored usernames, passwords, and user roles in text format. For most real-world applications, a properties file is not adequate storage for security information. For a variety of reasons, a directory server is often a much better choice. One reason is that real-world enterprise applications can be accessible to a large number of users -- often thousands of users, especially if the application exposes part of its functionality to customers and suppliers. It isn't efficient to frequently search through randomly stored information in a text file, but a directory server is optimized for such searches.
Another reason is demonstrated by the properties file in Part 1, Listing 6, which combines users and roles. In a real-world access control application, you would typically want to define and maintain information about users and roles separately, which makes it easier to maintain a user base. A directory server provides you almost infinite flexibility to change or update user information, for example to reflect job promotions or new hires. See Resources to learn more about the uses and benefits of directory servers.
If you want to store user information in an LDAP directory, you need to understand a few things about the directory setup. This article does not provide a complete introduction to LDAP (see Resources for that). Instead, it introduces the basic concepts you should know before attempting to use Acegi with LDAP directories.
An LDAP directory stores information in the form of a tree of nodes, as shown in Figure 1:
Figure 1. Tree structure of an LDAP directory
In Figure 1, the name of the root node is org. The
root node can wrap data related to different enterprises. For example, the
manufacturing enterprise developed in the first part of this series is shown as
the immediate child node of the root org node. The
manufacturing enterprise has two child nodes named
departments and partners.
The partners child node wraps different types of
partners. The three shown in Figure 1 are customers,
employees, and suppliers.
Note that all three types of partner can act as users of the enterprise system.
Each type of user has a different business role to play and therefore has
different rights to access the system.
Similarly, the departments node contains different
departments of the manufacturing enterprise -- such as the child nodes
engineering and marketing.
Each department node also contains one or more groups of users. In
Figure 1, the engineers group is a
child node of the engineering department.
This is assuming that the children of each department represent a group of users.
Therefore, the children of department nodes have different users as their members.
For example, all engineers working in the engineering department are members of
the engineers group within the
engineering department.
Finally, notice the last child node of the departments
node in Figure 1. specialUser is a
user, not a group. In this directory setup, users like
alice and bob would normally
be contained in the partners node. I have included the
special user in the departments node to demonstrate
Acegi's flexibility in allowing users to reside anywhere in an LDAP directory.
Later in the article, you will learn how to configure Acegi to accommodate
specialUser.
LDAP uses the concept of a distinguished name (DN) to identify the particular nodes in an LDAP tree. Each node has a unique DN, which contains its complete hierarchical information. For example, look at Figure 2, which shows the DNs of some of the nodes introduced in Figure 1:
Figure 2. Distinguished names of nodes in an LDAP directory
First, notice the DN of the root node in Figure 2. Its DN is
dc=org, which is an attribute-value pair associated
with the root org node. Every node can have a number of
attributes associated with it. The dc attribute stands
for "domain component" and is defined by LDAP RFC 2256 (see
Resources for links to official RFC documentation). A
root node in an LDAP directory is normally represented as a domain component.
Each LDAP attribute is defined by an RFC. LDAP allows the use of many attributes to create a DN, but the examples in this article only use the following four:
-
dc(domain component) -
o(organization) -
ou(organizational unit) -
uid(user ID)
The examples use dc to denote domains,
o for organization names, ou
for different units of the organization, and uid for
users.
Because org is the root node, its DN only needs to
specify its own name (dc=org). By contrast, the DN of
the manufacturingEnterprise node is
o=manufacturingEnterprise,dc=org. As you move down the
tree of nodes, the DN of each parent nodes is included in the DN of its child
nodes.
LDAP groups together related attribute types in the form of object classes. For
example, an object class named organizationalPerson
contains all the attributes that define a person working in an organization (for
example, title, common name, postal address, and so on).
Object classes use inheritance, which means LDAP defines base classes to hold commonly used attributes. Child classes then extend the base classes to use the attributes defined therein. A single node in an LDAP directory can use a number of object classes. The examples in this article use the following object classes:
- The top object class is the base class for all object classes in LDAP.
- The domain object class is used when other object classes are not
suitable for an object. It defines a set of attributes, any of which can be used
to specify an object. Its
dcattribute is mandatory.
- The organization object class represents organization nodes, such as
manufacturingEnterprisein Figure 2.
- The organizationalUnit object class represents units within the
organization, such as the
departmentsnode and its child nodes in Figure 1.
- The groupOfNames object class represents a group of names, such as the
names of people working in a department. It has a
memberattribute, which can contain a list of users. All group nodes in Figure 1 (such as theengineersnode) use thememberattribute to specify members of the group. Moreover, the examples use theou(organizational unit) attribute of thegroupOfNamesobject class to specify the business role of a group.
- The organizationalPerson object class represents a person in an
organization (such as the
alicenode in Figure 1).
In real-world applications, you normally host a lot of information about your system's users in an LDAP directory. For example, you store the username, password, job title, contact information, and payroll information for every user. For the sake of simplicity, the following examples show you how to store only the username and password.
As previously mentioned, the examples use ApacheDS, an open source LDAP directory server, to demonstrate how Acegi works with LDAP directories. They also use an open source LDAP client called JXplorer to execute simple directory operations like hosting information on ApacheDS. See Resources to download ApacheDS and JXplorer and learn more about how the two work together.
Creating a root node in ApacheDS
To create the tree of nodes shown in Figure 1, you must first
create the root node org in ApacheDS. ApacheDS provides
an XML configuration file for this purpose. The XML configuration file defines a
set of beans that you can configure to customize the directory server's behavior
according to your application requirements. Here I explain only the configuration
required to create a root node.
You can find the XML configuration file named
server.xml in the conf
folder of your ApacheDS installation. When you open the file, you see a number of
bean configurations similar to Acegi's filter configuration. Look for a bean named
examplePartitionsConfiguration. This bean controls
partitions in ApacheDS. When you create a new root node, you actually create a new
partition in an LDAP directory.
Edit the examplePartitionConfiguration bean to create
the root org node, as shown in Listing 1:
Listing 1. Edited form of the examplePartitionConfiguration bean configuration
<bean id="examplePartitionConfiguration" class=
"org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration"
>
<property name="suffix"><value>dc=org</value></property>
<property name="contextEntry">
<value>
objectClass: top
objectClass: domain
dc: org
</value>
</property>
<!-- Other properties of the examplePartitionConfiguration bean, which you don't
need to edit. -->
</bean>
|
Listing 1 edits two properties of the
examplePartitionConfiguration bean:
- A property named
suffixthat defines the DN of the root entry.
- A property named
contextEntrythat defines the object class that the rootorgnode will use. Notice that the rootorgnode uses two object classes:topanddomain.
The source code
download for this article includes the
edited form of the server.xml file. If you want to follow along with the example,
copy the server.xml file from the source code into its correct location in your
ApacheDS installation, which is the conf folder.
Figure 3 is a screenshot showing how JXplorer displays the root node once it is created in ApacheDS:
Figure 3. The root node displayed by JXplorer
The next step in setting up the LDAP server is to populate it with information about your users and groups. You can use JXplorer to create nodes in ApacheDS one by one, but it is much easier to simply populate the server using the LDAP Data Interchange Format (LDIF). LDIF is a well-known format recognized by most LDAP implementations. The composition of LDIF files is well-documented in developerWorks articles, so I won't explain it in detail here. (See Resources to learn more about LDIF.)
Instead, you can see the LDIF file in the
source
code download that represents the users and
departments shown in Figure 1. You can use JXplorer to import
the LDIF file into ApacheDS. To import the LDIF file, use the
LDIF menu on JXplorer, as shown in Figure 4:
Figure 4. Importing an LDIF file into ApacheDS
Once you have imported the LDIF file into ApacheDS, your JXplorer displays the tree of user nodes and department nodes, as shown in Figure 1. Now you are ready to begin configuring Acegi to communicate with your LDAP server.
Configuring Acegi for an LDAP implementation
Recall from Part 1 that Acegi uses the Authentication Processing Filter (APF) for authentication. APF performs all back-end authentication processing tasks, such as extracting the username and password from a client request, reading the user's parameters from the back-end user base, and using the information to authenticate the user.
You configured APF for a properties file implementation in Part 1. Now you have stored your user base in an LDAP directory, so you must configure the filter somewhat differently to talk to your LDAP directory. Start by looking at Listing 2, which shows how the APF filter was configured for a properties file implementation in the "Authentication Processing Filter" section of Part 1:
Listing 2. Configuring APF for a properties file
<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="/index.jsp" />
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
</bean>
|
Looking at Listing 2, recall that you provided four parameters to APF. You only
need to reconfigure the first parameter (the
authenticationManager) for storage in an LDAP server.
The other three parameters remain the same.
Configuring the authentication manager
Listing 3 shows how to configure Acegi's authentication manager to communicate with an LDAP server:
Listing 3. Configuring Acegi's authentication manager for LDAP
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="ldapAuthenticationProvider" />
</list>
</property>
</bean>
|
In Listing 3,
org.acegisecurity.providers.ProviderManager is the
manager class that manages Acegi's authentication process. To do its job, the
authentication manager requires one or more authentication providers. You can use
the provider property of the manager bean to configure one or more providers.
Listing 3 includes only one provider, the LDAP authentication provider.
The LDAP authentication provider handles all communication with your back-end LDAP directory. You must also configure it, as discussed next.
Configuring the LDAP authentication provider
Listing 4 shows the configuration for the LDAP authentication provider:
Listing 4. Configuring the LDAP Authentication Provider
<bean id="ldapAuthenticationProvider"
class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
<constructor-arg><ref local="authenticator"/></constructor-arg>
<constructor-arg><ref local="populator"/></constructor-arg>
</bean>
|
Note that the name of the LDAP authentication provider class is
org.acegisecurity.providers.ldap.LdapAuthenticationProvider
.
Its constructor takes two parameters in the form of two
<constructor-arg> tags, as shown in
Listing 4.
The first parameter to the LdapAuthenticationProvider
constructor is authenticator, which authenticates a
user with the LDAP directory by verifying the user's username and password. Once
the user is authenticated, the second parameter,
populator, retrieves information about the user's
access rights (or business roles) from the LDAP directory.
The following sections show you how to configure the authenticator and populator beans.
The authenticator bean checks whether a user exists in
the LDAP directory with a given username and password. Acegi provides an
authenticator class named
org.acegisecurity.providers.ldap.authenticator.BindAuthenticator,
which performs the required function of checking the username and password of the
user.
Configure the authenticator bean as shown in Listing
5:
Listing 5. Configuring the authenticator bean
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<property name="userDnPatterns">
<list>
<value>uid={0},ou=employees,ou=partners</value>
<value>uid={0},ou=customers,ou=partners</value>
<value>uid={0},ou=suppliers,ou=partners</value>
</list>
</property>
<property name="userSearch"><ref local="userSearch"/></property>
</bean>
|
In Listing 5, the BindAuthenticator constructor takes
one parameter in the form of a
<constructor-arg> tag. The name of the
parameter in Listing 5 is initialDirContextFactory.
This parameter is actually another bean, which you will learn how to configure in
just a moment.
For now, just know that the purpose of the
initialDirContextFactory bean is to specify an initial
context for later search operations. The initial context is a DN that specifies a
particular node within the LDAP directory. Once you specify the initial context,
all later search operations (such as locating a particular user) take place within
the child nodes of that node.
For example, look at the partners node back in
Figure 2, whose DN is
ou=partners,o=manufacturingEnterprise,dc=org. If you
specify the partners node as the initial context, Acegi
looks for users only among child nodes of the partners
node.
In addition to configuring the BindAuthenticator
constructor, you must also configure two properties of the
authenticator bean (the two
<property> tags in
Listing 5).
The first <property> tag defines a
userDnPatterns property, which wraps a list of one or
more DN patterns. A DN pattern specifies a number of LDAP nodes that have
something in common (such as all the child nodes of the
employees node in Figure 2).
Acegi's authenticator constructs one DN from each DN pattern configured in the
userDnPatterns property of the
authenticator bean. For example, look at the first DN
pattern configured in Listing 5, which is
uid={0},ou=employees,ou=partners. The
authenticator bean replaces the
{0} with the username supplied by the user (say,
alice) during authentication. After replacing
{0} with the username, the DN pattern becomes a
relative DN (RDN), uid=alice,ou=employees,ou=partners,
which needs an initial context to become a DN.
For example, look at alice's entry in
Figure 2. This entry is the first child of the
employees node. Its DN is
uid=alice,ou=employees,ou=partners,o=manufacturingEnterprise,
dc=org.
If you use o=manufacturingEnterprise,dc=org as an
initial context and append it after the RDN
uid=alice,ou=employees,ou=partners, you get alice's DN.
After constructing the user's DN from a DN pattern in this way, the
authenticator sends the DN and the user's password to
the LDAP directory. The directory checks whether the DN exists with a correct
password. If so, the user is authenticated. This process is called bind
authentication in LDAP terminology. LDAP offers other authentication
mechanisms, but the examples here only use bind authentication.
If the DN created by the first DN pattern does not exist in the directory, the
authenticator bean tries the next DN pattern configured
in the list. In this way, the authenticator bean tries
all DN patterns to construct the correct DN of the user who is asking to be
authenticated.
Recall from the earlier section called
"LDAP directory
setup" that I allowed for a bit of
flexibility in storing user information in the LDAP directory. I did this by
creating a special user (specialUser) within the
departments node shown in Figure
1.
If you try to create the DN of the special user using any of the DN patterns
configured in Listing 5, you will find that none of the
patterns is suitable to create the DN of the special user. As a result, when that
user tries to log in, Acegi's authenticator bean is
unable to construct the correct DN and therefore is unable to authenticate the
user.
Acegi handles special cases like this one by allowing you to specify search filters. The authenticator bean uses search filters to find users that it cannot authenticate by constructing a DN from the DN patterns.
The second <property> tag in
Listing 5 has a
<ref> child tag, which refers to a bean
named userSearch. The
userSearch bean specifies the search query. Listing 6
shows how to configure the userSearch bean to handle
special users:
Listing 6. Configuring a search query to search for special users
<bean id="userSearch"
class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<constructor-arg>
<value>(uid={0})</value>
</constructor-arg>
<constructor-arg>
<ref local="initialDirContextFactory" />
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
Parameters of the search query
Listing 6 shows that the userSearch bean is an
instance of a class named
org.acegisecurity.ldap.search.FilterBasedLdapUserSearch,
whose constructor takes three parameters. The first parameter specifies the node
where the authenticator searches for the users. The
value of the first parameter is ou=departments, which
is an RDN that specifies the departments node shown in
Figure 2.
The second parameter, (uid={0}), specifies a search
filter. Because you are using the uid attribute to
specify users, you can find a user by looking for a node whose
uid attribute has a particular value. As you can guess,
zero in curly brackets simply tells Acegi to replace
{0} with the username of the user to be authenticated
(in this case specialUser).
The third parameter is a reference to the same initial context that I introduced
while discussing the BindAuthenticator constructor in
Listing 5. Recall that once the initial context has been
specified, all later search operations take place within the child nodes of that
initial context node. Note that the RDN specified as the value of the first
parameter in Listing
5
(ou=departments) is prepended before the initial
context.
In addition to these three constructor parameters, the
userSearch bean shown in Listing 6 also takes a
property named searchSubtree. If you specify its value
as true, the search operation includes the sub tree
(that is, all children, grandchildren, great grandchildren, etc.) of the node you
specify as the value of the first constructor parameter.
The configuration of the authenticator bean is
complete. The next section looks at the configuration of the
populator bean, also shown in
Listing 4.
The populator bean reads the business roles of a user
already authenticated by the authenticator bean.
Listing 7 shows the XML configuration of the populator
bean:
Listing 7. XML configuration for the populator bean
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<property name="groupRoleAttribute">
<value>ou</value>
</property>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
In Listing 7, the populator bean takes two arguments
in its constructor, as well as a property named
groupRoleAttribute. The first constructor parameter
specifies the initial context that the populator bean
uses to read the business roles of an authenticated user. It is not mandatory to
use the same initial context for both the authenticator
and populator beans. You can configure a separate
initial context for each one.
The second constructor argument specifies an RDN that the populator prepends
before the initial context. In this way, the RDN forms the DN of a node that
contains groups of users, such as the departments node.
The groupRoleAttribute property of the
populator bean specifies the attribute that holds data
about the business roles of members of the group. Recall from the section about
setting up the LDAP directory that you stored
information about the business roles of each group in an attribute named
ou. You then configured ou
as a value of the groupRoleAttribute property, as shown
in Listing 7.
As you can guess, the populator bean searches through
the LDAP directory to find the nodes of the groups to which an authenticated user
belongs. It then reads the values attached to the ou
attributes of the group nodes to learn the user's authorized business roles.
This completes the configuration of the populator
bean. So far, you have used an initial context in three places: in
Listing 5, Listing 6, and
Listing 7. Next you will learn how to configure the
initial context.
Configuring the initial context
Listing 8 shows how to specify an initial context in Acegi:
Listing 8. XML configuration of an initial context
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://localhost:389/o=manufacturingEnterprise,dc=org"/>
<property name="managerDn">
<value>cn=manager,o=manufacturingEnterprise,dc=org</value>
</property>
<property name="managerPassword">
<value>secret</value>
</property>
</bean>
|
The name of Acegi's initial context class in Listing 8 is
org.acegisecurity.ldap.DefaultInitialDirContextFactory,
which is a factory class included in Acegi. Acegi internally uses this class to
construct objects of other classes that handle directory operations like searching
through the directory. You must specify the following when configuring the initial
context factory:
- The network address of your LDAP directory and your root directory node as a
constructor parameter. The node you configure in the initial context is taken as
the root node. This means all later operations (such as
search) are performed on the subtree defined by the root node.
- A DN and password, defined as
managerDnandmanagerPasswordproperties, respectively. Acegi must have the DN and password to authenticate itself with your directory server before it can perform any search operations.
You have learned how to host your user base in an LDAP directory and how to configure Acegi to use the information from the LDAP directory to authenticate your users. The next section digs deeper into Acegi's Authentication Processing Filter to see how its newly configured beans manage the process of authentication.
Authentication and authorization
Once APF is configured, it is ready to begin talking with the LDAP directory to authenticate a user. Some of the steps APF follows in its communication with the directory will be familiar to you from Part 1, where I showed you how this filter works with different services for the purpose of user authentication. The sequence diagram shown in Figure 5 is very similar to the one you saw in Part 1, Figure 3:
Figure 5. APF authenticates an LDAP user
Steps 1 through 9 are the same regardless of whether APF is using a properties file for internal authentication or communicating with an LDAP server. The first nine steps are recapped here, and then you can continue into the events specific to LDAP starting with Step 10:
- The previous filter in the filter chain passes request, response, and filter
chain objects to APF.
- APF creates an authentication token with the username, password, and other
information fetched from the request object.
- APF passes the authentication token to the authentication manager.
- The authentication manager may contain one or more authentication providers.
Each provider supports exactly one type of authentication. The manager checks
which of its providers support the authentication token received from APF.
- The authentication manager passes the authentication token to the provider
suitable for authentication.
- The authentication provider extracts the username from the authentication
token and passes it to a service called user cache service. Acegi maintains a
cache of users who have been authenticated. The next time the user signs in,
Acegi can load his or her details (such as username, password, and privileges)
from the cache instead of reading from back-end data storage. This improves performance.
- The user cache service checks whether details of the user exist in the cache.
- The user cache service returns the details of the user to the authentication
provider. If the cache does not contain user details, it returns null.
- The authentication provider checks whether the cache service returned details
of the user or null.
-
From here on, authentication processing becomes specific to LDAP. If the
cache returned null, the LDAP authentication provider passes the username
(extracted in Step 6) and password to the
authenticatorbean configured in Listing 5.
- The
authenticatorcreates user DNs using the DN patterns configured in theuserDnPatternsproperty of Listing 5. It tries all the available DN patterns one by one by creating a DN from a DN pattern and sending it along with the user's password (fetched from the user's request) to the LDAP directory. The LDAP directory checks whether the DN exists and the password is correct. If any of the DN patterns works, the user is said to be bound with the LDAP directory and theauthenticatormoves on to Step 15.
- If none of the DN patterns work (which means the user does not exist with the
given password at any of the locations specified by the DN patterns), the
authenticatorsearches for the user in the LDAP directory according to the search query configured in Listing 6. If the LDAP directory cannot find the user, authentication fails.
- If the LDAP directory finds the user, it returns the user's DN back to the
authenticator.
- The
authenticatorsends the user's DN and password to the LDAP directory to check whether the user's password is correct. If the LDAP directory finds that the password is correct, the user is said to be bound with the LDAP directory.
- The
authenticatorsends the user information back to the LDAP authentication provider.
- The LDAP authentication provider transfers control to the
populatorbean.
- The
populatorsearches for groups the user belongs to.
- The LDAP directory returns the user's role information to the
populator.
- The
populatorreturns the role information to the LDAP authentication provider.
- The LDAP authentication provider returns details of the user (along with information about user's business roles) back to APF. The user is now successfully authenticated.
The last three steps (Steps 21, 22, and 23) are the same regardless of authentication method.
You have seen the steps by which APF authenticates a user. The next step is to check whether a successfully authenticated user is authorized to access a requested resource. This is the job of Acegi's Interceptor Filter (IF). This section shows you how to configure IF to implement an access control policy.
Recall that you configured IF in Part 1, Listing 7. The Interceptor Filter maps resources with roles, which means that only users having the required role can access a given resource. To demonstrate the business roles of different departments of the manufacturing enterprise, Listing 9 adds another role to the existing IF configuration:
Listing 9. Configuring the interceptor filter
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/protected/engineering/**=ROLE_HEAD_OF_ENGINEERING
/protected/marketing/**=ROLE_HEAD_OF_MARKETING
/**=IS_AUTHENTICATED_ANONYMOUSLY
</value>
</property>
</bean>
|
In Listing 9, IF takes three parameters. The first and third parameter are the
same ones originally configured in Part 1. The second parameter (a bean named
accessDecisionManager) has been added.
The accessDecisionManager bean is responsible for
making authorization decisions. It uses the access control definitions provided by
the third parameter shown in Listing 9 to make authorization (or access control)
decisions. The third parameter is
objectDefinitionSource.
Configuring the access decision manager
The accessDecisionManager decides whether a user is
allowed to access a resource. Acegi provides a number of access decision managers,
which vary in how they make access control decisions. This article explains only
the workings of one access decision manager, which is configured in Listing 10:
Listing 10. Configuring the access decision manger
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter"/>
<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
|
In Listing 10, the accessDecisionManager bean is an
instance of a class named
org.acegisecurity.vote.AffirmativeBased. The
accessDecisionManager bean takes just one parameter,
which is a list of voters.
In Acegi, voters determine whether a user is allowed to access a particular
resource. When queried by the accessDecisionManager, a
voter has three options: it can vote access-granted, access-denied, or abstain
from voting if it is not sure.
The different types of access decision managers differ in how they interpret
voter decisions. The AffirmativeBased access decision
manager shown in Listing 10 implements simple decision logic: if any voter casts
an affirmative vote, it allows the user to access the requested resource.
Acegi provides several types of voter implementation. The
accessDecisionManager passes information about an
authenticated user (including the user's business roles) and the
objectDefinitionSource object to a voter. The example
here uses two types of voter, RoleVoter and
AuthenticatedVoter, as shown in Listing 10. Now
consider the logic of each voter:
-
RoleVoter votes only if it can find a role starting with the prefix
ROLE_in a line inside theobjectDefinitionSourceobject. IfRoleVotercannot find any such line, it abstains from voting; if it finds a matching role among the business roles of a user, it votes access-granted; if it cannot find a matching role, it votes access-denied. In Listing 9, there are two roles with the prefix ofROLE_:ROLE_HEAD_OF_ENGINEERINGandROLE_HEAD_OF_MARKETING.
-
AuthenticatedVoter votes only if it finds lines with some predefined role
in the
objectDefinitionSourceobject. In Listing 9, there is one such line:IS_AUTHENTICATED_ANONYMOUSLY. Anonymous authentication means that the user could not be authenticated. On finding this line, theAuthenticatedVoterchecks whether some of the non-protected resources (that is, resources not included in any line with theROLE_prefix) can be accessed by an anonymously authenticated user. IfAuthenticatedVoterfinds that the requested resource is non-protected and theobjectDefinitionSourceobject allows the non-protected resource to be accessed by an anonymously authenticated user, it votes access-granted; otherwise, it votes access-denied.
This article provides an example application that demonstrates the LDAP and Acegi
concepts you have learned so far. The LDAP-Acegi application displays an index
page that presents engineering and marketing documents to properly authenticated
users. As you will see, the LDAP-Acegi application allows user
alice to view engineering documents and user
bob to view marketing documents. It also allows a
special user to view both engineering and marketing documents. All of this was set
up at the beginning of the article when you configured the LDAP directory server.
Download the example application to begin exploring it
now.
In this article, you have learned how to host user and business role information in an LDAP directory. You have also learned in detail how to configure Acegi to interact with an LDAP directory and implement an access control policy. In the next installment of this series, I will show you how to configure Acegi to secure access to your Java classes.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for this article | j-acegi2.zip | 10KB | HTTP |
Information about download methods
Learn
-
Securing
Java applications with
Acegi series: Get an introduction to using Acegi Security System to secure Java
enterprise applications.
- "Securing Java applications with Acegi, Part 1: Architectural overview and security filters" (Bilal Siddiqui, developerWorks, March 2007): This article introduces you to the architecture and components of Acegi Security System.
- "Securing Java applications with Acegi, Part 3: Access control for Java objects" (Bilal Siddiqui, developerWorks, September 2007): This article demonstrates access control for Java objects.
- "Securing Java applications with Acegi, Part 4: Protecting JSF applications" (Bilal Siddiqui, developerWorks, February 2008): This article helps you configure JSF and Acegi to work together in a servlet container and explores how JSF and Acegi components cooperate with one another.
- "Introduction to LDAP: Part 1: Installation and simple Java LDAP programming
(Fred Simmons and Jeng Yoong Tan, developerWorks, April 2005): A detailed
introduction to using LDAP for Java development.
- "Storing
Java objects in Apache Directory Server, Part 1"
(Bilal Siddiqui, developerWorks, May 2006): Learn more about ApacheDS.
- "Java security evolution and concepts, Part 1: Security nuts and bolts"
(Raghavan Srinivas, developerWorks, May 2000): Refresh your memory about the
basics of Java security.
-
Role Based Access Control (RBAC): The
National Institute of Standards and Technology hosts information about RBAC
standards and working groups.
-
LDAP specification: The Internet Engineering
Task Force hosts the current LDAP specification. Also see the following RFC's of
the LDAP family of specifications:
- RFC 2256 explains the object classes and attribute types of LDAPV3.
- RFC 4513 details the LDAP authentication methods and security mechanism.
- RFC 2254 describes the representation of search filters in LDAP.
- RFC 2253 describes UTF-8 string representation for distinguished names in LDAP.
-
Directory-info.com: An excellent
resource for learning about LDAP and directories in general.
-
developerWorks Java technology zone:
Thousands of articles about every aspect of Java programming.
Get products and technologies
-
ApacheDS:
Download it from Apache.org.
-
JXplorer: A Java-based, open-source LDAP
client used extensively in this article.
Discuss
-
developerWorks
blogs: Get
involved in the
developerWorks community.
Bilal Siddiqui is an electronics engineer, an XML consultant, and the co-founder of WaxSys, a company focused on simplifying e-business. After graduating in 1995 with a degree in electronics engineering from the University of Engineering and Technology, Lahore, he began designing software solutions for industrial control systems. Later, he turned to XML and used his experience programming in C++ to build Web- and Wap-based XML processing tools, server-side parsing solutions, and service applications. Bilal is a technology evangelist and a frequently-published technical author.




