Historically, JBoss AS has provided the DynamicLoginConfig service to specify your security domain configuration (JAAS login modules). Starting JBoss AS 5.0, we provide a simplified xml version of that as follows:
You will need to create a xxx-jboss-beans.xml file and then you can define your login modules as follows:
===================================
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="urn:jboss:bean-deployer:2.0">
<application-policy xmlns="urn:jboss:security-beans:1.0" name="web-test">
<authentication>
<login-module code = "org.jboss.security.auth.spi.UsersRolesLoginModule"
flag = "required">
<module-option name = "unauthenticatedIdentity">anonymous</module-option>
<module-option name="usersProperties">u.properties</module-option>
<module-option name="rolesProperties">r.properties</module-option>
</login-module>
</authentication>
</application-policy>
<application-policy xmlns="urn:jboss:security-beans:1.0" name="ejb-test">
<authentication>
<login-module code = "org.jboss.security.auth.spi.UsersRolesLoginModule"
flag = "required">
<module-option name = "unauthenticatedIdentity">anonymous</module-option>
<module-option name="usersProperties">u.properties</module-option>
<module-option name="rolesProperties">r.properties</module-option>
</login-module>
</authentication>
</application-policy>
</deployment>
==================================
We still support the DynamicLoginConfig mbean definition approach also. But the afore mentioned approach is simpler.
DZone Article: http://server.dzone.com/articles/security-features-jboss-510
Frequently Asked Questions:
1. Where do I place the xxx.properties files for the UsersRolesLoginModule?
You can place them under the conf directory.
This blog is a personal book on Security/ IDM related thoughts/opinions. The blog posts are a personal opinion only and neither reflect the views of current/past employers nor any OTHER person living/dead on this planet.
Google Site Search
Friday, May 15, 2009
AS5.1: Specifying custom call back handler
In JBoss Application Server v5.1, you can specify a custom call back handler to be used in authentication in any of the following 3 ways:
1) Specify an attribute "CallbackHandlerClassName" in the JaasSecurityManagerService mbean definition in conf/jboss-service.xml
http://www.jboss.org/community/wiki/JaasSecurityManagerService
2) Specify a system property with the fully qualified class name of the call back handler
-Dorg.jboss.security.callbackhandler=org.somefqn
3) Inject an instance of the callback handler into the JNDISecurityManagement bean in the deploy/security/security-jboss-beans.xml
=================================================================================
<bean name="MyCustomCBH" class="fqn of your class" />
<bean name="JNDIBasedSecurityManagement"
class="org.jboss.security.integration.JNDIBasedSecurityManagement">
<property name="callBackHandler">'<inject bean="MyCustomCBH"/></property>
</bean>
=================================================================================
Unfortunately, the custom callback handler feature is broken in JBoss AS5.0, 5.0.1 and was fixed in JBAS-6747
NOTE: JBAS-6747 is fixed.
1) Specify an attribute "CallbackHandlerClassName" in the JaasSecurityManagerService mbean definition in conf/jboss-service.xml
http://www.jboss.org/community/wiki/JaasSecurityManagerService
2) Specify a system property with the fully qualified class name of the call back handler
-Dorg.jboss.security.callbackhandler=org.somefqn
3) Inject an instance of the callback handler into the JNDISecurityManagement bean in the deploy/security/security-jboss-beans.xml
=================================================================================
<bean name="MyCustomCBH" class="fqn of your class" />
<bean name="JNDIBasedSecurityManagement"
class="org.jboss.security.integration.JNDIBasedSecurityManagement">
<property name="callBackHandler">'<inject bean="MyCustomCBH"/></property>
</bean>
=================================================================================
Unfortunately, the custom callback handler feature is broken in JBoss AS5.0, 5.0.1 and was fixed in JBAS-6747
NOTE: JBAS-6747 is fixed.
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
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
Subscribe to:
Posts (Atom)