Level: Introductory Bilal Siddiqui (bsiddiqui@xml4java.com), Freelance consultant, WaxSys
25 Sep 2007 Bilal Siddiqui continues his introduction to Acegi
Security System by showing you how to secure access to instances of your
Java™ classes. Learn why you need to secure access to your Java classes, how
Spring creates and secures instances of your Java classes, and how to configure
Acegi to incorporate class security in your Java applications.
This series of articles is an introduction to using Acegi Security
System to secure Java enterprise applications.
Part 1
of this series introduced Acegi and explained how to use its built-in security
filters to implement a simple, URL-based security system.
Part 2
showed you how to write an access control policy and store it in an LDAP directory
server, as well as how to configure Acegi to interact with the directory server to
implement your access control policy. This third article in the series
demonstrates how to use Acegi to secure access to Java class instances in your
enterprise applications.
I'll start by briefly explaining when you might need to secure access to your
Java classes, including a couple of typical enterprise application scenarios for
reference. For further background, I explain how Spring's inversion of control
(IOC) framework creates instances of Java classes, which are accessible from your
JSPs or servlets. I also introduce the important concept of bean proxies,
which Spring uses to filter access to Java objects. Finally, I will show you how
to configure Acegi's method security interceptor to control access to your Java
classes. I continue with this article in the series by enhancing the sample
application from
Part 2 to
incorporate support for secure Java objects.
Because this discussion builds on the previous two articles in the series, I
frequently reference the discussion and examples from
Part 1
and Part 2.
It may be helpful to open these installments in additional browser windows before
you continue.
Use cases for securing Java classes
You might recall that I talked briefly about
enterprise application security
at the beginning of this series. In that introductory discussion, I mentioned a
scenario in which URL security would not be enough:
Consider a PDF document containing the data about a particular product
manufactured by the manufacturing company. Part of the document contains design
data meant to be edited and updated by the company's design department. Another
part of the document contains production data, which production managers will use.
For a scenario like this one, you would need to implement more fine-grained
security applying different access rights to different portions of the document.
Before moving on, let's consider a couple more application scenarios that might
require you to secure individual classes in addition to implementing URL security.
Business automation
Workflows in business automation applications consist of processes. For example,
the workflow of performing a blood test in a pathology laboratory could consist of
a number of steps, with each step being considered a business process:
- A staff member collects the blood sample from a patient and assigns an ID
number to the sample.
- A laboratory technician performs the required test on the sample and prepares
the test results.
- A qualified pathologist writes a test report based on the prepared test
results.
You can clearly see that each process is executed by a separate user who is
authorized to execute the process. Someone who is not authorized to perform a
process should not be able to execute the process. For example, the lab technician
should only prepare test results and should not be able to write the test report.
Authorized business processes are common to almost all business automation
applications. Normally, you would implement each business process as a Java class,
and all the classes would need to be secured by means of an appropriate access
control policy.
Business-to-business integration
Business-to-business (B2B) integration refers to the common scenario where two
business entities need to expose specific functionality to each other. For
example, a hotel might expose its room reservation functionality to a tour
operator, who could then use it to reserve an available hotel room for a tourist.
As a partner, the tour operator might also have a special room reservation rate.
In this case, the hotel's reservation system would have to authenticate the
operator before allowing access to the selected classes for reserving rooms at the
special rate.
Creating Java objects in Spring
Now you have an idea of why it's important to be able to secure access to Java
class instances. Before introducing Acegi's functionality for this advanced level
of security, I want to refresh your memory about some of the key features of the
Spring framework, which you need to understand to follow the upcoming examples.
Let's start by configuring some Java classes and instantiating them. As you might
recall from
Part
1,
Java classes are configured in Spring's XML configuration file. Configuring a Java
class in a Spring configuration file is exactly the same as configuring an Acegi
filter, so I won't explain this step. Instead, just take a look at Listing 1,
which shows the configuration for a bean named
publicCatalog:
Listing 1. Acegi XML configuration file
<beans>
<bean id="publicCatalog"
class="com.catalog.PublicCatalog" />
<!--Other bean tags -->
<beans>
|
It's important that you understand how Spring's IOC framework reads information
about your Java classes from the XML configuration file and instantiates them. You
might recall that in the
first article
of this series, I used a web.xml file to configure a
<listener> tag pointing to a class named
ContextLoaderListener. The
ContextLoaderListener loads Spring's IOC framework and
creates Java objects. You can see all this by referring back to
Part 1, Listing
8.
It is also depicted in Figure 1:
Figure 1. Loading Spring's IOC
framework and creating Java objects
Now let's consider those steps in detail:
- When you initiate your Acegi application, the servlet container (in this case
Apache Tomcat) creates a servlet context, which holds information about
application resources such as JSP pages and classes.
- The servlet container notifies the
ContextLoaderListener class that your application is
being started.
- The
ContextLoaderListener class creates a Web
application context to hold information about the Spring-specific resources of
your application. With the Spring's IOC framework, you can load your own custom
application context. To create an application context, it uses a context loader
class named ContextLoader, which loads the
application context.
- In case the application does not want to define its own application context,
it can use a class named
XMLWebApplicationContext,
which is part of the Spring framework and provides functionality to process
Spring's XML configuration files. Acegi applications use Spring's XML
configuration file, so this article discusses only the application context
represented by the XMLWebApplicationContext class. In
the case of this example, the context loader instantiates the
XMLWebApplicationContext class, which represents the
application context for your Acegi application. The context loader also sets a
reference of the servlet context (created in Step 1) into the Web application context.
- The
XMLWebApplicationContext class parses your XML
configuration file for information about your Java classes and loads the
information into its internal objects.
- The
XMLWebApplicationContext class instantiates all
the Java classes specified in your XML configuration file. The
XMLWebApplicationContext class checks whether a Java
bean configured in the XML configuration file depends on some other Java
objects. If so, the XMLWebApplicationContext class
first instantiates the beans on which other beans depend. In this way, the
XMLWebApplicationContext class creates instances of
all the beans defined in the XML configuration file. (Note that Step 6 assumes
that none of the beans configured in the XML configuration file need to be
secured. A later section explains the additional steps that have occurred
between Step 5 and Step 6 to secure access to Java beans created here.)
- The
XMLWebApplicationContext class stores all beans
in an array.
You've seen how to load bean definitions from an XML configuration file and
create instances of Java classes. Next, I'll introduce you to the Spring bean
proxy and explain its importance when securing access to Java class instances.
Using bean proxies
The previous section discussed how Spring's IOC framework instantiates Java
objects. In order to secure access to your Java objects, Spring's IOC framework
uses the concept of bean proxies. This section first shows you how to
configure a bean proxy and then demonstrates how Spring's IOC framework creates
the proxy object.
Configuring a proxy for a Java object
If you want to create a proxy bean, the Spring IOC framework requires you to
configure an instance of a proxy creator bean. Spring's IOC framework uses the
proxy creator to create proxy objects. Listing 2 is the configuration file for the
proxy creator bean, used to secure a Java object named
privateCatalog:
Listing 2. Proxy bean configuration
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>privateCatalog</value>
<!--Names of other beans to be proxied -->
</list>
</property>
<property name="interceptorNames">
<list>
<value>privateCatalogSecurityInterceptor</value>
</list>
</property>
</bean>
|
As you can see in Listing 2, the <bean>
tag has a class attribute whose value is
org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator.
The BeanNameAutoProxyCreator class is part of the
Spring IOC framework and automatically creates bean proxies. The Spring framework
provides an interface named BeanPostProcessor, which
represents an extensibility mechanism to allow applications to write their own
logic to create bean proxies. Spring's
BeanNameAutoProxyCreator class implements the
BeanPostProcessor interface and provides all the proxy
creation logic that you need to secure your Java classes. Therefore, you don't
need to implement the BeanPostProcessor interface for
the purpose of this article.
While creating bean proxies, the
BeanNameAutoProxyCreator class creates proxies for all
the beans defined by the beanNames property (see the
first <property> child of the
<bean> tag in
Listing 2). The beanNames
property takes a list of bean names in a
<list> tag. In
Listing 2, I have configured only one bean named
privateCatalog for which I want to create a proxy.
Now look at the second <property> child
of the <bean> tag in
Listing
2. It specifies a property named
interceptorNames, which wraps the names of one or more
interceptors. I will discuss the concept of interceptors in detail later in the
article. For the moment, just note that an interceptor can intercept a user's
request and implement an access control policy before allowing the user to access
the bean.
Now you've seen how to configure proxy for the beans that you want to protect.
Next, you'll learn how Spring's IOC framework internally creates proxy objects for
the beans of your application.
Spring IOC takes over
In Step 5 and Step 6 of "Creating Java objects in Spring"
you saw how the XMLWebApplicationContext class reads
bean definitions from an XML configuration file and then creates bean instances.
Before creating a bean instance, the
XMLWebApplicationContext class checks whether the XML
configuration file contains any proxy creator bean configuration (that is, a bean
that implements the BeanPostProcessor interface). If it
finds one, it asks the proxy creator to create bean proxies for the beans that you
wish to secure.
Now consider how the proxy creator internally creates proxy objects:
- The proxy creator (that is, the
BeanNameAutoProxyCreator class) loads all the bean
names specified in the beanNames property file that
you configured in Listing 2.
- The proxy creator uses the bean names to load their respective Java classes
using the
class attribute of each bean definition.
- The proxy creator creates an instance of the interceptor specified in the
interceptorNames property shown in Listing 2.
- Finally, the proxy creator creates an instance of a class named
Cglib2AopProxy, passing all the bean names (from Step
2) and the interceptor (from Step 3) to the
Cglib2AopProxy class. The
Cglib2AopProxy class is part of the Spring framework
and is used to generate dynamic proxy objects. In this case, the
Cglib2AopProxy class creates the proxy objects you
will need to control access to your secure beans.
The Cglib2AopProxy class implements two interfaces
named AOPProxy and
MethodInterceptor. The
AOPProxy interface is part of the Spring framework and
represents the actual bean that you want to proxy, so it exposes the same methods
that your bean exposes. The MethodInterceptor
interface, also part of the AOP specification, has methods that receive control
whenever a user tries to access the bean that you have proxied. This means the
MethodInterceptor interface handles requests from users
to access a proxied bean. Given that the Cglib2AopProxy
class implements both AOPProxy and
MethodInterceptor interfaces, you can guess that it
offers the complete functionality of representing a proxied bean as well as
handling user requests to access a proxied bean. (See the
Resources section for links to articles that discuss
AOP.)
After executing the previous steps, you have the required proxy objects. So the
XMLWebApplicationContext class stores the proxies of
secure beans (instead of the actual beans) in the same array that you saw in Step
7 of "Creating Java objects in Spring."
Accessing proxied Java objects
In the previous sections, you learned how Spring creates public beans and private
beans. For the purpose of this article, you can think of public beans as unsecured
and private ones as being secured by proxy. Now let's look at the sequence of
steps a client application must follow to access public and private beans.
Listing 3 shows the XML configurations for two beans named
publicCatalog and
privateCatalog. The
publicCatalog bean is meant for public access so it
does not need a bean proxy. The privateCatalog bean is
meant to be accessed only by designated users, so it must be secured. I have
included a bean proxy configuration for the
privateCatalog bean in Listing 3:
Listing 3. XML configuration for publicCatalog and privateCatalog beans
<beans>
<bean id="publicCatalog" class="sample.PublicCatalog"/>
<bean id="privateCatalog" class="sample.PrivateCatalog"/>
<!-- proxy configuration for privateCatalog bean -->
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>privateCatalog</value>
<!--Names of other beans to be proxied -->
</list>
</property>
<property name="interceptorNames">
<list>
<value>privateCatalogSecurityInterceptor</value>
</list>
</property>
</bean>
<beans>
|
Listing 4 shows the code that an application could use to access the
publicCatalog and
privateCatalog Java beans configured in Listing 3. Note
that the Java code shown in Listing 4 could exist in a JSP page or in another bean
contributing to a server-side Java application.
Listing 4. Client application code to access secure and insecure Java beans
//Step 1: Fetching an instance of the application context
XMLWebApplicationContext applicationCtx =
WebApplicationContextUtils.getWebApplicationContext(
this.getServletConfig().getServletContext());
//Step 2: Fetching an insecure bean from the application context
PublicCatalog publicCatalog =
(PublicCatalog) applicationCtx.getBean("publicCatalog");
//Step 3: Calling a method of the insecure bean
String publicData = publicCatalog.getData();
//Step 4: Fetching a secure bean from the application context
PrivateCatalog privateCatalog =
(PrivateCatalog) applicationCtx.getBean("privateCatalog");
//Step 5: Calling a method of the secure bean
String privateData = privateCatalog.getData();
|
Consider the steps in Listing 4 in greater detail:
-
Step 1: Fetching an instance of the application context
When an application wants to access a Java bean configured in an XML
configuration file, it has to fetch the
XMLWebApplicationContext object that you saw earlier
in Step 4 of "Creating Java objects in Spring." The
XMLWebApplicationContext object contains references
to all the Java beans configured in the XML configuration file.
-
Step 2: Fetching an insecure bean from the application context
You now have a reference to the
XMLWebApplicationContext object. The
XMLWebApplicationContext class exposes a method named
getBean(), which takes the name of a bean and finds
the bean in the array it prepared in Step 7 of
"Creating Java objects in Spring." In this case, the
bean is publicCatalog (which is not proxied), so the
XMLWebApplicationContext returns the actual bean.
-
Step 3: Calling a method of the insecure bean
Now you can call any method of the
publicCatalog bean you got in Step 2. For example,
the getData() method call shown in Listing 4 executes
without any access control and returns the catalog data to the application.
-
Step 4: Fetching a secure bean from the application context
Fetching a secure bean is similar to fetching insecure beans. The only
difference is that when you try to fetch a secure bean by calling the
getBean() method, you get a proxy of the secure
object instead of the actual object. The proxy is the same object created by the
Spring framework that I explained in Step 4 of "Spring IOC takes over."
-
Step 5: Calling a method of the secure bean
When you call a method of a secure bean, the proxy object that you got in
Step 4 dispatches a method invocation request to the
interceptor. The interceptor processes the method invocation request by checking
if the user trying to access the method has appropriate access rights.
You should have a clear picture now of how the Spring framework creates Java
objects and how client applications interact with them. With this background, it
will be easier to understand and utilize Acegi's method security interceptor,
which is the topic of the next section.
Configuring Acegi's method security
interceptor
Whenever an application tries to access a method of a bean secured using Acegi
Security System, the request is automatically transferred to Acegi's method
security interceptor. The purpose of the method security interceptor is to control
access to methods of secure Java beans. The interceptor uses Acegi's
authentication and authorization framework to confirm whether a user is authorized
to call a method of the secure Java bean, and then responds accordingly.
Listing 5 is a sample configuration of Acegi's method security interceptor:
Listing 5. Configuration of Acegi's method security interceptor
<bean id="privateCatalogSecurityInterceptor"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>
sample.PrivateCatalog.getData=ROLE_HEAD_OF_ENGINEERING
<!-- Roles required by other beans -->
</value>
</property>
</bean>
|
The interceptor configuration shown in Listing 5 contains three properties that
you need to configure in order to secure access to your Java beans:
authenticationManager,
accessDecisionManager, and
objectDefinitionSource.
Recall that you configured the authenticationManager
property while
configuring the authentication processing filter
in the first article of this series. The purpose of the
authenticationManager property is to authenticate a
user.
You learned about the
accessDecisionManager
property in the second article in this series. The access decision manager is
responsible for making authorization decisions. The method security interceptor
uses the authenticationManager and
accessDecisionManager properties to authenticate and
authorize a user before allowing access to a secure bean.
Now look at the objectDefinitionSource property
configured in Listing 5. It is similar to the
objectDefinitionSource
property shown in Part 1. Whereas the original
objectDefinitionSource
contained URLs like /protected/* and
/**, the
objectDefinitionSource property in Listing 5 specifies
class and method names; for instance,
sample.PrivateCatalog is the name of a class that you
proxied earlier and getData is the name of a method to
which you want to control user's access.
Whenever the user accesses the getData() method of the
PrivateCatalog bean, control is automatically
transferred to the interceptor. The interceptor uses the Acegi framework to check
whether the user's business role is
ROLE_HEAD_OF_ENGINEERING (in this example). If it is,
the interceptor allows access to the getData() method.
If interceptor finds that the user does not have the
ROLE_HEAD_OF_ENGINEERING, it denies access.
The next section looks at a sample Acegi application that implements the concepts
you have learned so far.
A sample Acegi application
The source code download for this article contains a
sample application called AcegiMethodSecurity, which you can configure and deploy
as follows:
- Populate your LDAP server with user information. The
sample application download contains an LDIF file that
has user information ready to be loaded into your LDAP server. See
"Populating the server"
in Part 2 for a refresher on importing the LDIF file into your LDAP server. Note
that this application features the same users (
alice,
bob, and specialUser) that
you worked with in Part 2.
- Copy the acegiMethodSecurity.war file from the source code download of this
article into the webapps directory of your Tomcat installation.
- Copy Acegi's jar files into the WEB-INF/lib folder of the sample application.
(See
"Deploy and run the application"
from Part 1 for a refresher on this.)
- Download the cglib-full-2.0.2.jar file and copy it in WEB-INF/lib folder of
the sample application.
Start Tomcat and you should be all set to try the sample application.
Get to know the sample application
You can invoke the sample application by accessing the
http://localhost:8080/acegiMethodSecurity URL from your browser. The
AcegiMethodSecurity application displays an index page that contains two links
(Catalog and Login), as shown in Figure 2:
Figure 2. Main page of the
sample application
When you click on the application's Catalog link, it will ask you to log
in. If you log in as alice or
specialUser, the sample application serves you the
complete catalog, including both public and private data. That's because
back in Listing 5, you configured the method security
interceptor to allow users having
ROLE_HEAD_OF_ENGINEERING to access the private catalog,
and both alice and
specialUser have that access. On the other hand, if you
log in as bob, the sample application shows you only
public data.
Assigning additional roles to
authenticated users
This section demonstrates an enhanced version of the sample application. The
enhanced version shows how Acegi lets you temporarily assign additional roles to
an authenticated user at runtime.
You might need additional roles whenever a secure bean (such as the
privateCatalog bean of
Listing 3) needs to access a remote resource. As an
example, you might consider that your secure bean needs to access a remote
application through Java's Remote Method Invocation (RMI) framework or a Web
service. The user accessing your secure bean will not possess the business role
that the remote application expects from a user trying to access it.
In this case, Acegi first checks whether the user is authorized to access the
secure bean. After checking, Acegi allows the user to access the secure bean. When
the secure bean attempts to access the remote service, it will need additional
business roles. If the user who accessed the secure bean does not have the
additional role(s), the secure bean will not be able to successfully access the
remote service.
The run-as-replacement mechanism
The Acegi framework provides a simple mechanism called run-as-replacement
that allows you to configure one or more additional roles for an authenticated
user only for the duration of a method invocation call. You can use the run-as
replacement to configure additional roles for a secure bean to access remote
applications. This means whenever the secure bean needs to access a remote
application, Acegi will load additional roles for the user, thus allowing the
secure bean to access the remote application.
Listing 6 shows an enhanced configuration of the method security interceptor that
you saw earlier in Listing 5. The enhanced version uses
the run-as-replacement mechanism.
Listing 6. Enhanced configuration of Acegi's method security interceptor
<bean id="privateCatalogSecurityInterceptor"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="runAsManager">
<bean id="runAsManager"
class="org.acegisecurity.runas.RunAsManagerImpl">
<property name="key">
<value>myKeyPass</value>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value>
sample.PrivateCatalog.getData=ROLE_HEAD_OF_ENGINEERING,RUN_AS_MANAGER
</value>
</property>
</bean>
|
You can see two enhancements in Listing 6 (as compared to
Listing 5) shown in bold. The first enhancement is a
property named runAsManager. The purpose of the
runAsManager property is to add support for dynamically
adding roles to the authenticated user. For this purpose, the
runAsManager property contains the definition of a bean
named RunAsManagerImpl. The
RunAsManagerImpl bean becomes active only when it finds
a role with RUN_AS_ prefix in the role definitions of a
method in objectDefinitionSource. For example, the role
definition of the PrivateCatalog.getData() method (the
second bold enhancement in Listing 6) has a
RUN_AS_MANAGER role.
The RunAsManagerImpl bean contains a property named
key, which wraps a cryptographic key used to ensure
that the additional role can only be generated as part of the run-as-replacement
procedure.
When a user calls the getData() method, the
RunAsManagerImpl bean becomes active and creates an
additional role named RUN_AS_MANAGER, thus enabling the
getData() method to access the remote application.
Enhanced method security
This article's source code download includes a sample
application named EnhancedAcegiMethodSecurity, which
demonstrates the run-as-replacement mechanism and procedure. The application
displays an index page with a Catalog link. If you click the Catalog link,
you will be asked to log in.
Once you have logged in, the
EnhancedAcegiMethodSecurity application will provide
you with the complete information about the logged-in user and that user's roles.
For instance, if you log in as alice or
specialUser, you will be shown all the business roles
of the user including the additional temporary
RUN_AS_MANAGER role.
Conclusion
In this series of articles, I introduced you to using Acegi Security
System to enforce both URL-based security and method-based security. You learned
how to design access control policies and host them in a directory server, how to
configure Acegi to communicate with a directory service, and how to make
authentication and authorization decisions based on the access control policies
hosted there.
This article focused on using method-based security to
protect Java class instances. It also explained how Acegi and Spring internally
create and proxy Java objects and how bean proxies work to implement access
control. The article includes a couple of sample applications that you can use to
further explore the concepts you have learned in this series. See the
Resources section to learn more about securing Java
applications with Acegi.
Download | Description | Name | Size | Download method |
|---|
| Source code for this article | j-acegi3-source.zip | 36KB | HTTP |
|---|
Resources Learn
Discuss
About the author  | |  | 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.
|
Rate this page
|