Google Site Search

Google
 

Tuesday, May 5, 2009

JBoss AS/JBossMC: Adding secure behavior to POJOs

Let us take a look at a particular use case that I had to inject passwords from an out of band password management scheme into POJOs. The use case was to eliminate clear text passwords from xml files in the JBoss Application Server v5.1 and beyond. Since POJOs are the norm in JBAS5.x, it was important to figure out a mechanism to inject the passwords into POJO properties in a generic/non-intrusive way. The AOP Lifecycle Callback mechanism described in the JBoss Microcontainer documentation (http://www.jboss.org/file-access/default/members/jbossmc/freezone/docs/2.0.x/userGuide/ch05.html) empowered me to achieve my use case.

The reason I used lifecycle callbacks rather than aspects is that I needed a generic way to specify the properties where the password needed to get injected and all I cared for was the password was injected when the bean was created/started and ready for use. Hence the lifecycle callbacks fitted perfectly.

A very good use case for the aspects would be if I wanted to store values in POJO properties in an encrypted manner - hence a setter would probably encrypt the data. That use case is for another day to implement.

Let us walk through my use case implementation:

Step 1: Annotation

Let us look at a POJO definition in the JBoss AS. I can take the example of the JBoss Messaging SecurityStore bean. It has a property called as "suckerPassword" that needs a password value.

==========
<bean name="SecurityStore" class="org.jboss.jms.server.jbosssx.JBossASSecurityMetadataStore">
<!-- default security configuration -->
<property name="defaultSecurityConfig">
<![CDATA[
<security>
<role name="guest" read="true" write="true" create="true"/>
</security>
]]>
</property>
<property name="suckerPassword">CHANGE ME!!</property>
<property name="securityDomain">messaging</property>
<property name="securityManagement"><inject bean="JNDIBasedSecurityManagement"/></property>
</bean>
============

Given this, I can apply an annotation in the bean definition file (messaging-jboss-beans.xml) as follows:

=================
<bean name="SecurityStore" class="org.jboss.jms.server.jbosssx.JBossASSecurityMetadataStore">
<!-- default security configuration -->
<property name="defaultSecurityConfig">
<![CDATA[
<security>
<role name="guest" read="true" write="true" create="true"/>
</security>
]]>
</property>
<property name="securityDomain">messaging</property>
<property name="securityManagement"><inject bean="JNDIBasedSecurityManagement"/></property>
<!-- Password Annotation to inject the password from the common password
utility -->
<annotation>@org.jboss.security.integration.password.Password(securityDomain=messaging,
methodName=setSuckerPassword)</annotation>
</bean>
==================

As you can see, I just used the annotation definition in the xml file. I have also removed the definition of "suckerPassword" in the bean definition.

The annotation is just a regular Java annotation as follows:

====================
package org.jboss.security.integration.password;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface Password
{
/**
* Security Domain
* Defaults to other
* @return
*/
String securityDomain() default "other";

/**
* Name of the method
* that represents the password
* @return
*/
String methodName();
}
=================================

It is not a magical annotation.

The annotation in the bean definition basically lets the microcontainer apply the annotation to the bean.

Step 2: AOP Lifecycle callbacks

First, I needed to add the lifecycle elements into the security-jboss-beans.xml file since these were
security callbacks. In my use case, the PasswordMaskManagement bean is the one that interacts with the
out of band password management system. I declare the lifecycle callback advices and then inject the
password mask management bean into them.

========================
<!-- Password Mask Management Bean-->
<bean name="JBossSecurityPasswordMaskManagement"
class="org.jboss.security.integration.password.PasswordMaskManagement" >
<property name="keyStoreLocation">password/password.keystore</property>
</bean>



<!-- Support for @Password -->
<lifecycle-configure xmlns="urn:jboss:aop-beans:1.0"
name="PasswordRegistrationAdvice"
class="org.jboss.security.integration.password.PasswordLifecycleCallback"
classes="@org.jboss.security.integration.password.Password"
manager-bean="AspectManager"
manager-property="aspectManager">
<property name="passwordManagement"><inject bean="JBossSecurityPasswordMaskManagement"/></property>
</lifecycle-configure>

<lifecycle-create xmlns="urn:jboss:aop-beans:1.0"
name="PasswordCreateDestroyAdvice"
class="org.jboss.security.integration.password.PasswordLifecycleCallback"
classes="@org.jboss.security.integration.password.Password"
manager-bean="AspectManager"
manager-property="aspectManager">
<property name="passwordManagement"><inject bean="JBossSecurityPasswordMaskManagement"/></property>
</lifecycle-create>

<lifecycle-install xmlns="urn:jboss:aop-beans:1.0"
name="PasswordStartStopAdvice"
class="org.jboss.security.integration.password.PasswordLifecycleCallback"
classes="@org.jboss.security.integration.password.Password"
manager-bean="AspectManager"
manager-property="aspectManager">
<property name="passwordManagement"><inject bean="JBossSecurityPasswordMaskManagement"/></property>
</lifecycle-install>
=============================

Let us take a look at the advice.

===============================================
package org.jboss.security.integration.password;

import java.lang.reflect.Method;

import org.jboss.aop.joinpoint.Invocation;
import org.jboss.aop.joinpoint.MethodInvocation;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.logging.Logger;
import org.jboss.metadata.spi.MetaData;

public class PasswordLifecycleCallback
{
private static final Logger log = Logger.getLogger(PasswordLifecycleCallback.class);

private PasswordMaskManagement passwordManagement = null;

/**
* Set the Password Mask Management bean
* @param passwordManagement
*/
public void setPasswordManagement(PasswordMaskManagement passwordManagement)
{
this.passwordManagement = passwordManagement;
}

/**
* Bind the target on setKernelControllerContext, unbind on any other method provided that
* the invocation has a Password annotation.
*
* @param invocation the invocation
* @return the result
* @throws Throwable for any error
*/
public Object invoke(Invocation invocation) throws Throwable
{
MethodInvocation mi = (MethodInvocation) invocation;
KernelControllerContext context = (KernelControllerContext) mi.getArguments()[0];

boolean trace = log.isTraceEnabled();
Password passwordAnnotation = (Password) invocation.resolveClassAnnotation(Password.class);
if( trace )
log.trace("Checking method: "+mi.getMethod()+", bindingInfo: "+passwordAnnotation);

// If this is the setKernelControllerContext callback, set the password
if ("setKernelControllerContext".equals(mi.getMethod().getName()) && passwordAnnotation != null)
{
//Get the password
String securityDomain = passwordAnnotation.securityDomain();
char[] passwd = this.passwordManagement.getPassword(securityDomain);

Object target = context.getTarget();
this.setPassword(target, passwordAnnotation, passwd);
}
// If this is the unsetKernelControllerContext callback, unbind the target
else if( passwordAnnotation != null )
{
log.trace("Ignoring unsetKernelControllerContext callback");
}
else if ( trace )
{
log.trace("Ignoring null password info");
}

return null;
}

public void install(ControllerContext context) throws Exception
{
//Get the password
Password passwordAnnotation = readPasswordAnnotation(context);
boolean trace = log.isTraceEnabled();
if( trace )
log.trace("Binding into JNDI: " + context.getName() + ", passwordInfo: " + passwordAnnotation);

String securityDomain = passwordAnnotation.securityDomain();

char[] passwd = this.passwordManagement.getPassword(securityDomain);

if(passwd == null)
log.trace("Password does not exist for security domain=" + securityDomain);
//The bean in question is the target
String methodName = passwordAnnotation.methodName();
Object target = context.getTarget();
if(trace)
{
log.trace("Trying to set password on " + target + " with method :" + methodName);
}
this.setPassword(target, passwordAnnotation, passwd);
}

public void uninstall(ControllerContext context) throws Exception
{
//ignore
}


private Password readPasswordAnnotation(ControllerContext context) throws Exception
{
MetaData metaData = context.getScopeInfo().getMetaData();
if (metaData != null)
return metaData.getAnnotation(Password.class);
return null;
}

private void setPassword(Object target, Password passwordAnnotation, char[] passwd) throws Exception
{
Class<?> clazz = target.getClass();
String methodName = passwordAnnotation.methodName();
if(methodName == null)
throw new IllegalStateException("methodName " + methodName + " not configured on " +
"the Password annotation for target:" + clazz);
Method m = SecurityActions.getMethod(clazz, methodName);

try
{
m.invoke(target, new Object[] {passwd});
}
catch(Exception e)
{
log.trace("Error setting password on " + clazz + ". Will try the string version.");
m.invoke(target, new Object[] { new String(passwd)} );
}
}
}
===========================================================================

Now as beans go through the MC lifecycle, the advice is applied. If the beans contain the @Password annotation, then
as you can see, we inject the password (by getting it from the PasswordMaskManagement bean).

Conclusion
Here we have seen injection of passwords into beans using AOP lifecycle callbacks. JBoss AS 5.x ships with a @JndiBinding annotation that can similarly bind a POJO to JNDI. An user/developer can always inject similar behavior to beans.

To summarize, if you want to leverage the AOP lifecycles in a non-intrusive manner, you can use an annotation and an advice. Then just define them in the bean definition file xxx-jboss-beans.xml

No comments: