Spring AOP and MVC authorization

With Spring and a bunch of AOP I’ll show how it’s easy to add protectionsof your business components and notify the view that the user is not authorizedto perform a specific action.This example relies on Spring 2.0 and Spring MVC for the presentation layer.Let’s take an example of a service that executes jobs whose class is named JobExecutorService.This service has methods to add jobs in a Job queue, remove a job from the queueor stop a running job. We would like to authorize only owners of a job to remove the job or stop it. The jobs are persistent (to recover from failure).

1) Defining the authorization aspect
Let’s define in Spring’s application context a Job authorization advice and inject it a Job DAO to access persistent jobs.

<bean id="jobAuthorizationAspect" class="security.JobAuthorizationAspect">
<property name="jobDAO">
<ref bean="jobDAO" />
</property>
</bean>

Here’s below the simplified code of the bean advice which is just a POJOwhich benefits from injection.When the user doesn’t own the Job an AuthorizationException is thrown.We’ll see later how to deal with the exception.
public class JobAuthorizationAspect {
JobDAO jobDAO;
protected static final Log LOG =LogFactory.getLog(JobAuthorizationAspect.class);
public void setJobDAO(JobDAO jobDAO) {
this.jobDAO= jobDAO;
}
public void checkForAuthorization(Job job) {
boolean authorized = false;
UserContext userCtx = ContextMgr.getUserContext();
if (null != userCtx) {
Job job = jobDAO.getJobById(job.getId());
// If user launched Job, he's allowed to perform operations on it
if ((job.getState() == Job.STATE.RUNNING || job.getState() == Job.STATE.STOPPED || job.getState() == job.getState().ENDED) && job.getOwner().equals(userCtx.getPrincipal().getName())) authorized =true;
}
}
else {
LOG.warn("user context or job context have not been found");
}
if (! authorized ) {
LOG.warn("user not authorized");
throw new AuthorizationException("jobaction.notauthorized",new Object[]{},"this action is not allowed");
}
}

N.B
I used Spring to bind user’s context (which stores user information) to the current Thread via a ThreadLocal to easily retrieve user’s data stored in memory and avoid being tied to the Servlet API. ContextMgr classhas a static method to retrieve user’s context.Actually binding is done with a Spring MVC interceptor on controllers in the Spring Web Application Context(you could also use a Servlet filter):
<bean id="authurlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/jobs.htm">JobMultiActionController</prop>
<!-- Add additional URL mappings here -->
</props>
</property>
<property name="interceptors">
<list>
<ref bean="ContextInterceptor"/>
</list>
</property>
</bean>

The ContextInterceptor bean just retrieves user’s data from the HTTP session
and binds it to the current thread

2) Declaring the poincuts

Then we add pointcuts to execute the aspect. Here the pointcuts are defined with AspectJ expressions

<aop:config proxyTargetClass="true">
<aop:aspect id="beforeAdviceJobAuth" ref="jobAuthorizationAspect">
<aop:advice
kind="before"
method="checkForAuthorization" arg-names="job"
pointcut="(execution(* jobqueue.JobExecutorService.stopJob(..)) and args (job)) || ( execution(* jobqueue.JobExecutorService.removeJob(..)) and args(job)) "/>
</aop:aspect>
</aop:config>

So here we are weaving our jobAuthorizationAspect advice with the JobExecutorService bean (not detailed here).The proxyTargetClass attribute is here because the JobExecutorService is declared elsewehere in Spring’s applicationContext and injected and used directly in Spring MVC controllers not via its interfaces. But the bean’s class implements multiple interfaces (infrastructure interfaces like Observer )Spring’s default behaviour is when it detects that the class implements interfaces it to use Java dynamic proxies. So here we set this attribute to force CGLib proxying.The arg-names attribute is here to be as explicit as possible and make sure that our “before” advice is executed for the proper methods of JobExecutorService and that the Job argument is passed (by reference) to the “checkForAuthorization” method of our advice.

3) Dealing with the AuthorizationException

Spring has a mechanism that allows to intercept exceptions and deal with them.We could catch the error and redirects to a page displaying a generic authorization message,we’ll choose to extend this concept to return to the submitting view to display the message.Here’s the declaration of the AuthorizedExceptionResolver

<bean id="AuthorizedExceptionResolver" class="web.AuthorizedExceptionResolver">
<property name="order"><value>1</value></property>
</bean>

And a simplified version of the code (we’ll suppose that all our controllersare SimpleFormController ). :

public class RecoverableExceptionResolver implements HandlerExceptionResolver,Ordered,ApplicationContextAware {
private int order;
public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception e) {
if (e instanceof RecoverableException ) {
Map<Object,Object> messages = new HashMap<Object,Object>();
if (handler != null && handler instanceof AbstractController ) {
AbstractController controller = (AbstractController)handler;
messages.put("errormessage",controller.getApplicationContext().getMessage(((AuthorizedException)e).getMessageKey(),"not authorized",((AuthorizedException)e).getMessage(),request.getLocale()));
request.setAttribute("messages",messages);
}
// If SimpleFormController return internally to its view
if (null != handler && handler instanceof SimpleFormController) {
return new ModelAndView(controller.getFormView());
}
}
return null;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}

Here I made the assumption that the command (object that maps the HTTP submitted parameters)has been bound to user’s session in the controller (Spring has mechanism to do this)and that the form’s view first check that if the form’s command is in the user’s sessionit uses it to populate the form’s fields.We’ll also suppose that the controller’s form view has a box that displays messages whenfound in the request context under the messages attribute (like flash boxes of ROR).

Conclusion:
AOP is the perfect tool to protect business components in a non-intrusive way.Thanks to Spring MVC framework techniques we can create a very flexible and genericway to handle authorizations and report authorization exceptions in our web layer. (using many of the Java EE cool buzz technos)

N.B: Sorry for my poor English…

Leverage Ant XML nature

I have been responsible at my current job position of the build of our JavaEE application. We had to build 3 different flavours of the same application. When I decided which build tool to use, I chose Ant since only Maven 1.0 had been released and I disliked writing logic in Jelly. If I had to choose today I’d probably go with Maven 2 because it becomes a standard for industrializing builds on Java and I have just found a decent documentation on it…

Our main application is built as an EAR file and I had to support packaging for different application servers. We decided to externalize dependencies (<dependencies> element), compilation (<javalibs> element) and packaging information (<application> element) in an XML description file. Then with the help of a XSL file, we generate dynamically an Ant build file and execute it. We reused Ant <fileset> and <zipfileset> to describe location of files or directories.

In the generation of the EAR, we used sensitive default behaviours to minimize configuration. Here are a few:

  • include dependencies (see element below) in the EAR unless scope is set to compile and add them to the MANIFEST.MF of each Java module.
  • add dependencies to the global CLASSPATH (there’s a compilation CLASSPATH for each targeted application server also)
  • include generated libraries in the EAR unless distinclude is set to false and add them to the MANIFEST.MF of each Java module. If generated libraries are scoped to a given Java EE module they are added in the WEB-INF/lib for a web module or are overlaid with the final EJB jar for EJB modules. Generated global libraries are also added to the global CLASSPATH for compilation.

The general strategy was to compile and build global (use by multiple Java EE modules) Jar files with the global Classpath and specific ones with the application server’s CLASSPATH and global CLASSPATH. This way global Jar files were compiled once for all the application servers.

It had some limitations:

  • We only supported Java EE modules of type web or ejb.
  • Global compiled Jar files could not depend on a specifig Jar files.
  • Compilation Classpath are not totally isolated for each Web module.

I like our approach since with a light XML description file we can now generate an EAR for many application servers. application.xml for the EAR and MANIFEST.MF for each Java modules are generated dynamically. Configuration is far less important than for a Maven’s Pom.xml. Ok we lack transitive dependencies, web site generations, synchronization with Eclipse but we didn’t really need them.Here’s how a build description file could look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project [
<!ENTITY LIBDIR "../lib">
<!ENTITY LIBDIR "../src">
]>
<project name="myapp" compilation="shared">
<dependencies>
<fileset dir="&amp;LIBDIR;/log4j/" includes="log4j-1.2.8.jar"/>
<fileset dir="&amp;LIBDIR;/xml" includes="jdom.jar"/>
<fileset dir="&amp;LIBDIR;/weblogic/8.1" includes="webservices.jar" scope="compile" target="weblogic"/>
</dependencies>
<javalibs source="1.4">
<javaproject dir="&amp;SRCDIR;/infra/" includes="core/**" name="infra"/>
<javaproject dir="&amp;SRCDIR;/ws" name="presentationws" module="presentationweb" target="weblogic"/>
<javaproject dir="&amp;SRCDIR;/wsaxis" name="presentationwsaxis" module="presentationweb" target="websphere"/>
<javaproject dir="&amp;SRCDIR;/wsclient" name="wsclient" distinclude="false">
<fileset dir="&amp;SRCDIR;/wsclient/config" includes="client-config.wsdd"/>
</javaproject>
<javaproject dir="&amp;SRCDIR;/Application/WebClient/src" name="wsclient" distinclude="false" sign="true">
<attribute name="Main-Class" value="wsclient.Main"/>
<attribute name="Class-Path" value="commons-httpclient-3.0.jar commons-logging.jar jaxrpc.jar mail.jar saaj.jar wsdl4j.jar"/>
</javaproject>
</javalibs>
<application target="weblogic" format="ear">
<zipfileset prefix="sql" dir="&amp;SRCDIR;/db" includes="*.sql"/>
<module name="presentationws" type="web" context="PresentationWeb">
<zipfileset prefix="WEB-INF" dir="&amp;SRCDIR;/config/PresentationWeb" includes="web.xml,weblogic.xml"/>
</module>
</application>
<application target="websphere" format="ear">
<zipfileset prefix="sql" dir="&amp;SRCDIR;/db" includes="*.sql"/>
<module name="presentationws" type="web" context="PresentationWeb">
<zipfileset prefix="WEB-INF" dir="&amp;SRCDIR;/config/PresentationWeb" includes="web.xml,ibm*.xml"/>
</module>
</application>
</project>

Java on FreeBSD

Hey, great ! Sun and FreeBSD agreed on Java distribution and now J2SDK can get distributed with FreeBSD. No more get linux-compat => get a JDK => get Java sources => Compile and package to run Java “natively” on FreeBSD (without Linux compatibility ).

It took at least 6 hours on my slow home gateway. Also it seems to run much faster, see the results of my very serious test (2nd invokation of java -version)

java version "1.5.0-p2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-p2-root_16_jan_2006_15_45)
Java HotSpot(TM) Client VM (build 1.5.0-p2-root_16_jan_2006_15_45, mixed mode)
real 0m3.232s
user 0m1.679s
sys 0m1.468s
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build diablo-1.5.0-b00)
Java HotSpot(TM) Client VM (build diablo-1.5.0_06-b00, mixed mode)
real 0m0.643s
user 0m0.556s
sys 0m0.070s