Java EE: Logging User Interaction the Aspect-Oriented Way using Interceptors

May 26th, 2014 by

Using dependency injection and aspect-oriented mechanisms like interceptors allow us to separate cross-cutting-concerns in our Java enterprise application, to control global aspects of our application and to avoid boilerplate code.

In the following short tutorial we’re going to create an aspect-oriented logger to protocol the initiating user, class and method called and the parameters passed to the method and finally we’re adding this interceptor to a sample RESTful web-service by adding a simple annotation.

Java EE 7 Around Invoce Interceptor

Java EE 7 Around Invoce Interceptor

 

Project Setup / Maven Archetype

Basically we need only one dependency here to be added to our pom.xml:

<dependency>
	<groupId>javax</groupId>
	<artifactId>javaee-web-api</artifactId>
	<version>7.0</version>
	<scope>provided</scope>
</dependency>

Otherwise there is a nice Maven archetype, org.codehaus.mojo.archetypes.webapp-javaee7 that allows us to create our project structure using mvn archetype:generate

mvn archetype:generate -Dfilter=webapp-javaee7
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[..]
Choose archetype:
1: remote -> org.codehaus.mojo.archetypes:webapp-javaee7 (Archetype for a web application using Java EE 7.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1

Java EE 6? The following tutorial works in Java EE 6 without any problem. The only difference is the RESTful webservice where we might need to add some more configuration.

Log Producer

The following producer allows us to use dependency injection to create a logger instance in every location of our application like this: @Inject Logger log;

Using a producer allows us to obtain information about the injection point and use this information to extract the package/class name and create a new logger instance with it (as I’m using GlassFish in this tutorial, we’re using the JULI/JDK logger) .

package com.hascode.tutorial;
 
import java.util.logging.Logger;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
 
public class LogProvider {
	@Produces
	public Logger createLogger(InjectionPoint ip) {
		return Logger.getLogger(ip.getMember().getDeclaringClass().getName());
	}
}

Around Invoke Interceptor

The following interceptor gets the session context and the logger from the producer above injected and uses the information gathered from the session context and the invocation context to protocol the following information:

  • The username of the initiating user .. e.g. fred
  • The object whose method is called .. e.g. com.hascode.tutorial.UserWebservice@67784ed2
  • The name of the method called .. e.g. getUser
  • The parameters passed to the method

One warning regarding the code below: For production we definitely should add some null-checks for the parameters (or simply use ObjectUtils.toString(obj, “null”) of Apache Commons Lang or something similar) and a fallback if no principal can be obtained from the session context e.g. by writing something like “anonymous user” to the logs.

Another thing to consider is the logging of all our parameters – it’s fine for the demonstration purpose in this tutorial but on a production system we definitely don’t want to write things like passwords, hashes or other fragments of sensitive information to our logs as this is one common vulnerability listed on the OWASP list.

Detailed information about this vulnerability can be found at the OWASP Wiki: Logging and Auditing Vulnerability.

When we’re finished writing our logs, we’re calling the invocation context’ proceed method to pass the control further the chain of interceptors in our application.

package com.hascode.tutorial;
 
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
 
public class ActionProtocolInterceptor {
	@Resource
	private SessionContext sessionCtx;
 
	@Inject
	private Logger log;
 
	@AroundInvoke
	protected Object protocolInvocation(final InvocationContext ic)
			throws Exception {
		StringBuilder sb = new StringBuilder("[");
		for (Object obj : ic.getParameters()) {
			sb.append(obj.toString());
			sb.append(", ");
		}
		sb.append("]");
		log.log(Level.INFO,
				"user {0} invoced {1} with method {2} and parameters: {3}",
				new Object[] { sessionCtx.getCallerPrincipal().getName(),
						ic.getTarget().toString(), ic.getMethod().getName(),
						sb.toString() });
		return ic.proceed();
	}
}

Applying the Interceptor to a Sample REST-Service

Finally the only thing we need to do is to add our interceptor to some executable code.

There are different possible ways to configure the interaction of our interceptor with our application:

  • @Interceptors: Specified on class or method level allows us to add one or more interceptors executed in the order of the definition e.g. @Interceptors({Interceptor1.class, Interceptor2.class..})
  • @ExcludeDefaultInterceptors: Disables declared default constructors for this location.
  • @ExcludeClassInterceptors: Disables class specified interceptors for this location.
  • Global Configuration via ejb-jar.xml: We’re able to add interceptors to our application on a global level and using wildcards .. e.g. for every EJB in the service package. The ejb-jar.xml should be put into WEB-INF or META-INF depending on the packaging format.
  • For more detailed information, please feel free to have a look at Oracle’s Java Documentation here.

The following RESTful webservice is easy summarized – it offers two callable methods, restricts its usage to users assigned to the group named user and our interceptor is applied using the @Interceptors annotation.

package com.hascode.tutorial;
 
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
 
@Stateless
@Path("/user")
@Interceptors(ActionProtocolInterceptor.class)
@RolesAllowed("user")
public class UserWebservice {
 
	@DELETE
	@Path("/{userId}")
	public Response removeUser(@PathParam("userId") String userId) {
		// do stuff..
		return Response.ok().build();
	}
 
	@GET
	@Path("/{userId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getUser(@PathParam("userId") String userId) {
		// do stuff..
		return Response.ok().build();
	}
}

The Logger in Action

We’re now ready to initiate a request to the RESTful webservice to see our logger in action.

When calling the url for the GET annotated method, we should see a login prompt like this. How to configure a realm and user accounts is describe in the Appendix A below.

Basic auth login prompt

Basic auth login prompt

Afterwards we should be able to view some similar excerpt in the domain logs:

[2014-05-25T15:47:08.582+0200] [glassfish 4.0] [INFO] [] [com.hascode.tutorial.ActionProtocolInterceptor] [tid: _ThreadID=127 _ThreadName=http-listener-1(4)] [timeMillis: 1401025628582] [levelValue: 800] [[
  user fred invoked com.hascode.tutorial.UserWebservice@67784ed2 with method getUser and parameters: [25C46B7E-5A22-4470-B048-593DCA9600ED, ]]]

This is what the log excerpt looks like in the GlassFish log viewer:

GlassFish Log Viewer

GlassFish Log Viewer

GlassFish Log Viewer - Log Details

GlassFish Log Viewer - Log Details

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/javaee7-aop-logging.git

Resources

Appendix A: Setup Basic Auth on a File-based Realm on GlassFish 4

Neither basic authentication nor using a file-based realm is something we’d like to use on a production system, but for a tutorial like this one, it should be sufficient ;)

Specifying Security Constraints

The following constraint, added to our web.xml (must be put in WEB-INF) enforces that an initiator of a request must be authenticated and assigned to the user role named user.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	version="3.1">
 
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>everything</web-resource-name>
			<url-pattern>/*</url-pattern>
		</web-resource-collection>
		<auth-constraint>
			<role-name>user</role-name>
		</auth-constraint>
	</security-constraint>
 
	<security-role>
		<role-name>user</role-name>
	</security-role>
</web-app>

Role-to-Group Mapping

Now we need only one more descriptor to map between roles assigned from the container to groups in our application.

Simply add the following sun-web.xml to src/main/webapp/WEB-INF

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD GlassFish Application Server 3.0 Servlet 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_3_0-0.dtd">
<sun-web-app>
        <security-role-mapping>
                <role-name>user</role-name>
                <group-name>user</group-name>
        </security-role-mapping>
        <class-loader delegate="true" />
</sun-web-app>

Add User Account to File Realm

We’re able to configure the user accounts for our file based realm in the GlassFish administration web console in the section Configurations > server-config > Security > Realms > file > Manage users.

There we may add a user, set the password and assign the group named user to him.

GlassFish 4 File Realm User Management

GlassFish 4 File Realm User Management

Appendix B: Complete REST Service Configuration

In Java EE 7 we don’t need to add some REST servlet to the web.xml but I wanted to modify the application path for the REST services that’s why I added the following Java configuration to the project that does nothing more than to register the prefix/path “rs” for the services.

package com.hascode.tutorial;
 
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
 
@ApplicationPath("/rs")
public class RESTConfiguration extends Application {
}

Tags: , , , , , , , , , , , , , ,

2 Responses to “Java EE: Logging User Interaction the Aspect-Oriented Way using Interceptors”

  1. Abhishek Says:

    Excellent! Crisp, clear and to the point.

  2. Nicholas Wright Says:

    Agreed. Nice, clear, example.
    Thank you!

Search
Categories