27 June 2007

Accessing j_username in OC4J form based authentication failures

A question on the OC4J OTN forum recently asked about how the error page used in form based authentication could get access to the username that was provided so an audit trail could be established.

Intuitively you'd expect that the form fields passed in from the logon form would be passed through to the error page when an authentication fails and the forward is done. However this is not the case. The request parameter map is empty.

The solution is to use an OC4J proprietary feature called a Form Auth Filter. This is a standard Servlet Filter that can be injected into the request path when form based authentication is performed. The filter will be called before the authentication is performed and has access to the full set of request parameters passed in from the j_security_check form.

http://download-west.oracle.com/docs/cd/B32110_01/web.1013/b28959/filters.htm#sthref150

To accomplish the task of making the supplied j_username available in the error page, a form auth filter can extract the j_username parameter and store it in the request as an attribute.

Then in the error handler defined for the form-auth (jsp, struts action, etc.) simply extract the request attribute and do what you want with it.

Here's a step by step example.

1. Create a web application that uses form-based-authentication

<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>web-inf/logon.jsp</form-login-page>
<form-error-page>web-inf/error.jsp</form-error-page>
</form-login-config>
</login-config>

2. Create a ServletFilter to remap the j_username request parameter

package demo.sab.otn.formauthfilter;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class FormAuthFilter implements Filter {
private FilterConfig _filterConfig = null;

public void init(FilterConfig filterConfig) throws ServletException {
_filterConfig = filterConfig;
}

public void destroy() {
_filterConfig = null;
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse)response;
HttpServletRequest req = (HttpServletRequest)request;

String j_username = req.getParameter("j_username") ;
if(j_username!= null) {
req.setAttribute("j_username", j_username);
}
chain.doFilter(req, res);
}
}

3. Create an orion-web.xml file to specify the ServletFilter as a FORMAUTH filter

<?xml version = '1.0' encoding = 'windows-1252'?>
<orion-web-app>
<web-app>
<filter-mapping>
<filter-name>FormAuthFilter</filter-name>
<dispatcher>FORMAUTH</dispatcher>
</filter-mapping>
</web-app>
<security-role-mapping name="secure" impliesAll="false">
<group name="oc4j-administrators"/>
</security-role-mapping>
</orion-web-app>
3. Access the j_username attribute from the error.jsp page

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<title>Proxy Authentication</title>
<link href="../css/blaf.css" rel="stylesheet" media="screen"/>
</head>
<body>
<h2>Error!</h2>
<%
String username = request.getAttribute("j_username")==null?
"" : (String)request.getAttribute("j_username");
%>
Error logging on as <%=username%>&nbsp;Try again.
<p>
<form method="POST" action="j_security_check">
<input type="text" name="j_username" value="<%=username%>"/>
<input type="password" name="j_password"/>
<input type="submit" value="logon"/>
</form>

</p>
</body>
</html>


Now give it a spin. When an authentication failure occurs, the error.jsp page displays the username that was last provided.


This simple example just demonstrates how to get access to the supplied j_username, what you then decide to do with it is up to you!

25 June 2007

Using Groovy to operate with OC4J Groups

Update: the client code referenced in this post, and a sample script to create an OC4J group and populate it with OC4J instances is available here:
The Oracle Application Server 10g Release 3 product exposes the concept of OC4J Groups. An OC4J group is a formal entity that represents a set of separate OC4J instances as a single unit. An OC4J group can be started, stopped; it can be configured and managed; deployment operations can be conducted against it. In all cases, the task performed on the group is is delegated out to the individual OC4J instances that reside within the group to be actioned.
To effectively support the management and configuration of an OC4J group, a cluster MBeanServer model is utilized in conjunction with a special MBean that represents an OC4J group.
The cluster MBeanServer is a specialized MBeanServer that works with the OPMN technology in Oracle Application Server so that it is always aware of all the individual MBeanServer instances running witihn the connected network topology.
The J2EEServerGroupMBean is an MBean that performs the task of exposing a group to MBean clients so that MBeans can be interacted with. The J2EEServerGroupMBean closely resembles the MBeanServer interface and enables clients to perform operations, get and set attributes on a specified MBean. When the tasks are executed on the J2EEServerGroupMBean, the J2EEServerGroupMBean will federate the operations to all the running MBeanServers in the OC4J instances that reside in the group.
A distinct difference from the standard MBeanServer interface is that tasks that have a return value will return a Map instead of an Object. The Map contains the results of the task execution from each of the OC4J instances that participated in the requested task. The OC4J instance name is used as the key for each entry in the Map and the value is the result of the executed task.
http://download-west.oracle.com/docs/cd/B31017_01/web.1013/e10288/oracle/oc4j/admin/management/farm/mbeans/J2EEServerGroupMBean.html
To use the J2EEServerGroupMBean, a client first connects to the OC4J cluster MBeanServer. The respective J2EEServerGroup MBean is located for the target group. Once a reference to the J2EEServerGroup MBean is obtained, it is used to execute operations or set and get attributes against any specific MBean that resides within the MBeanServers of the OC4J instances within the group.
Unfortunately, since the J2EEServerGroupMBean is an actual MBean itself that exposes methods to work on other MBeans, using it either from either direct Java code, or using it from Groovy as an GroovyMBean means that you need to resort to using the primitive invoke, setAttribute, getAttribute methods to perform tasks on a specified MBean.
Ideally, it’d be more productive and suit the scripting approach more naturally to be able to perform a task on an MBean through the simplified GroovyMBean approach but have it performed at the group level. When the tasks are executed on a specified MBean, the J2EEServerGroupMBean would be used under the covers to execute the invoke, setAttribute or getAttribute methods.
Making a Group GroovyMBean

It turns out that using the power of the Groovy meta-data model and Java subclassing, it’s possible to create an extension of the standard GroovyMBean (called OC4JgroupGroovyMBean in here) that does just that.
By passing a GroovyMBean object that wrappers the target MBean into an OC4JGroupGroovyMBean object, the OC4JGroupGroovyMBean can be made to look exactly like the GroovyMBean thus directly exposing all the attributes and operations from the underlying MBean.
Through overriding specific methods such as setProperty, getProperty and invokeMethod, the tasks are conducted through the specified J2EEServerGroupMBean instead of the standard MBeanServer connection and are therefore conducted against the J2EEServerGroup.

The example OC4JGroupGroovyMBean has a constructor of the following form:
public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection, ObjectName groupMBeanName,GroovyMBean mbean)
The first parameter contains the connection to an OC4J Cluster MBeanServer.
The second parameter is the ObjectName that represents the target J2EEServerGroup on which to execute the tasks.
The third parameter is a GroovyBean that has been created for the target MBean that is to be operated on.
At this point, it’s more illustrative to present a simple example. Lets consider that we have a two-instance installation of Oracle Application Server, on which we have a group called COLORS in which reside two OC4J instances, RED and BLUE.
Across the COLORS group, for each OC4J instance we wish to view the vendor and version of the JDK that is being used, and report the current memory that has been consumed by the running instance.
As a starting point, the OC4J MBean that contains the required information is the JVMMBean.
This can be obtained from an OC4J MBeanServer using the ObjectName “oc4j:j2eeType=JVM,name=single,J2EEServer=standalone”.
The target J2EEServerGroup will have an ObjectName of the form “ias:j2eeType=J2EEServerGroup,name=
import demo.oc4j.jmx.*;
// First create a GroovyMBean for the target MBean
client = new OC4JClient()
client.connect("service:jmx:rmi:///opmn://localhost/home", "oc4jadmin","welcome1")
jvm = client.helper.createGroovyMBean("oc4j:j2eeType=JVM,name=single,J2EEServer=standalone")
// Now create an OC4JGroupGroovyMBean around it
clusterClient = new OC4JClient();
clusterClient.connect("service:jmx:rmi:///opmn://localhost/cluster", "oc4jadmin","welcome1")
def group_jvm =
new OC4JGroupGroovyMBean(clusterClient.connection,
”ias:j2eeType=J2EEServerGroup,name=default_group",
jvm);
def memory = group_jvm.freeMemory;
memory.keySet().each() {
def mem = memory.get(it) / 1024 / 1024
println "$it\n\t-->free $mem MB\n"
}
// close the clients
client.close()
clusterClient.close()
Which when executed produces the following output:

ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=COLORS,J2EEServer=RED,ASInstance=j2ee_server.SERVER
-->free 454.37 MB
ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=COLORS,J2EEServer=BLUE,ASInstance=j2ee_server.SERVER
-->free 453.977 MB

The code for the OC4JGroupGroovyMBean is quite simple and just overrides a couple of the methods on the standard GroovyMBean.

The listing for OC4JGroupGroovyMBean example is below.
package demo.oc4j.jmx;

// Use wide imports just for saliency 
import groovy.lang.*;
import groovy.util.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import javax.management.*;
import oracle.oc4j.admin.management.farm.mbeans.proxies.*;

/**
 * This class is derived from the work done by the official 
 * Groovy development team at "The Codehaus - http://groovy.codehaus.org/"
 * 
 * The purpose is to use the same simple model of interacting with MBeans
 * from Groovy as if they were local object, but now at the OC4J Group level
 * via the J2EEServerGroupMBean.
 * 
 * This is specific to the OC4J Group mechanism and not general purpose.
 * 
 */
public class OC4JGroupGroovyMBean extends GroovyMBean {

    static Logger logger = Logger.getLogger(OC4JGroupGroovyMBean.class.getName());

    J2EEServerGroupMBeanProxy groupProxy = null;
    MBeanServerConnection clusterConnection = null;
    ObjectName groupMBeanName = null;
    
    // This overrides the operations map from the parent class because its a 
    // private field and it can't be accessed from here
    Map operations = new HashMap();

    /**
     * Construct an OC4JGroupGroovyMBean that represents the named MBean at 
     * the OC4J Group level.
     * @param clusterConnection -- connection to OC4J cluster domain
     * @param groupMBeanName -- string form of the J2EEServerGroupMBean name
     * @param mbean -- the GroovyMBean to perform operations on
     * @throws JMException
     * @throws IOException
     */
    public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection,
                                String groupMBeanName, 
                                GroovyMBean mbean) throws JMException, 
                                                          IOException {
        this(clusterConnection, new ObjectName(groupMBeanName), mbean);
    }
    
    /**
     * Construct an OC4JGroupGroovyMBean that represents the named MBean at 
     * the OC4J Group level.
     * @param clusterConnection -- connection to OC4J cluster domain
     * @param groupMBeanName -- ObjectName for the J2EEServerGroupMBean name
     * @param mbean -- the GroovyMBean to perform operations on
     * @throws JMException
     * @throws IOException
     */
    public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection,
                                ObjectName groupMBeanName,
                                GroovyMBean mbean) throws JMException, IOException {
        
        // Make this object look like the GroovyMBean for the specified MBean
        super(mbean.server(), mbean.name());
        
        this.clusterConnection = clusterConnection;
        this.groupMBeanName = groupMBeanName;

        // Populate the operations map needed to conduct the invoke operation
        try {
            operations = populateOperations(mbean);
        } catch (IntrospectionException e) {
            throw new JMException(e.getMessage());
        }
        
        // Create a dynamic proxy for the J2EEServerGroup
        groupProxy = (J2EEServerGroupMBeanProxy)
                MBeanServerInvocationHandler.newProxyInstance(
                clusterConnection, 
                groupMBeanName,
                J2EEServerGroupMBeanProxy.class,
                false);
    }

    /**
     * Get a set of properties from an MBean via a J2EEServerGroup
     * @param props
     * @return Map where v = instancename, t = String[] of values
     */
    public Map getProperties(Collection props) {
        String[] properties = (String[]) props.toArray(new String[0]);
        logger.log(Level.FINE, "getProperty, properties = " + properties);        
        Map ret = new HashMap();
        try {
            Map data = groupProxy.getAttributes(this.name(), properties);
            for(ObjectName id: data.keySet()) {
                AttributeList attributes = data.get(id);
                List values = new ArrayList();
                for (int i = 0; i <>
                    Attribute attribute= (Attribute) attributes.get(i);
                    values.add(attribute.getValue());
                }
                ret.put(id, values);
            }
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not access property: " + properties + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not access property: " + properties + ". Reason: " + e, e);
        }
        return ret;
    }
    
    /**
     * Get a property from an MBean via a J2EEServerGroup
     * @param property
     * @return Map of property values from each OC4J instance in the Group
     */
    public Map getProperty(String property) {
        logger.log(Level.FINE, "getProperty, property = " + property);        

        Map ret = null;
        try {
            ret = groupProxy.getAttribute(this.name(), property);
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e);
        }
        return ret;
    }
    
    /**
     * Set a property on an MBean via the J2EEServerGroupMBean
     * @param property
     * @param value
     */
    public void setProperty(String property, Object value) {
        logger.log(Level.FINE,"setProperty, property = " + property + ", value = " + value);        
        try {
            groupProxy.setAttribute(this.name(), new Attribute(property, value));
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e);
        }
    }

    /**
     * Set properties on the MBean via the J2EEServerGroupMBean 
     * @param properties
     * @return Map of results from each instance of setting the properties 
     */
    public Map setProperties(Map properties) {
        try {
            AttributeList attributes = new AttributeList();
            for(String key: properties.keySet()) {
                Attribute attribute = new Attribute(key, properties.get(key));
                attributes.add(attribute);
            }
            return groupProxy.setAttributes(this.name(), attributes);
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not set properties: " + properties+ ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not set properties: " + properties  + ". Reason: " + e, e);
        }
    }

    /**
     * Invoke a method on the J2EEServerGroupProxy
     * @param method
     * @param args
     * @return
     */
    public Object invokeMethod(String method, Object args) {
        logger.log(Level.FINE,"invokeMethod, method = " + method + ", args = " + args);        
        Object[] argArray = null;
        if (args instanceof Object[]) {
            argArray = (Object[]) args;
        } else {
            argArray = new Object[]{args};
        }
        // Locate the specific method based on the name and number of parameters
        String operationKey = createOperationKey(method, argArray.length);
        String[] signature = (String[]) operations.get(operationKey);
        
        if (signature != null) {
            try {
                return groupProxy.invoke(this.name(), method, argArray, signature);
            }
            catch (MBeanException e) {
                throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e.getTargetException());
            }
            catch (Exception e) {
                throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e);
            }
        } else {
            //todo: validate when/why this occurs, don't think we ever want to do this!
            return super.invokeMethod(method, args);
        }
    }
    

    /**
     * Populate a local operations Map with details from the target MBean
     * @param mbean
     * @return
     * @throws IOException
     * @throws IntrospectionException
     * @throws InstanceNotFoundException
     * @throws ReflectionException
     */
    private Map populateOperations(GroovyMBean mbean) throws IOException, 
                                                             IntrospectionException, 
                                                             InstanceNotFoundException, 
                                                             ReflectionException {
        Map operations = new HashMap();
            
        MBeanInfo beanInfo = mbean.server().getMBeanInfo(mbean.name());
        MBeanOperationInfo[] operationInfos = beanInfo.getOperations();
        for (int i = 0; i <>
            MBeanOperationInfo info = operationInfos[i];
            String signature[] = createSignature(info);
            String operationKey = createOperationKey(info.getName(), signature.length);
            operations.put(operationKey, signature);
         }
         return operations;
    }
    

}


20 June 2007

Published: Javadoc for OC4J MBeans

A Javadoc set has just been published on the OTN documentation site for the OC4J MBeans:

Oracle® Application Server JMX MBean Java API Reference 10g Release 3 (10.1.3)

We also published a Javadoc set for the relevant portions of our deployment area/JSR88 implementation:

Oracle® Application Server Deployment Java API Reference 10g Release 3 (10.1.3)

13 June 2007

Groovy + JMX documentation

Got a note recently from one of the Groovy developers that he'd authored some documentation around the use of JMX and Groovy. Very nicely he included examples of connecting to OC4J.

Good job Paul!

http://groovy.codehaus.org/Groovy+and+JMX

** Small update: I just tried the script and I couldn't get the code example for OC4J to work as it was listed on the site. I had to make a few minor alterations to get it to work for me. Here's the groovy script I have now which works against Groovy 1.0.

The script does work as it is shown against Groovy 1.1 which has an enhanced GroovyMBean constructor that can now takes the target MBean name in String form in addition to the earlier ObjectName form.

import oracle.oc4j.admin.jmx.remote.api.*
import javax.management.remote.*
import javax.management.*

def serverUrl = new JMXServiceURL('service:jmx:rmi://localhost:23791')
def serverPath = 'oc4j:j2eeType=J2EEServer,name=standalone'
def jvmPath = 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone'
def provider = 'oracle.oc4j.admin.jmx.remote'

def credentials = [
(JMXConnectorConstant.CREDENTIALS_LOGIN_KEY): 'oc4jadmin',
(JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY): 'welcome1'
]
def env = [
(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES): provider,
(JMXConnector.CREDENTIALS): credentials
]
//def MBeanServerConnection server = (MBeanServerConnection)JMXConnectorFactory.connect(serverUrl, env).mBeanServerConnection
def server = JMXConnectorFactory.connect(serverUrl, env).mBeanServerConnection
def serverInfo = new GroovyMBean(server, new ObjectName(serverPath))
def jvmInfo = new GroovyMBean(server, new ObjectName(jvmPath))
println """Connected to $serverInfo.node. \
Server started ${new Date(serverInfo.startTime)}.
OC4J version: $serverInfo.serverVersion from $serverInfo.serverVendor
JVM version: $jvmInfo.javaVersion from $jvmInfo.javaVendor
Memory usage: $jvmInfo.freeMemory bytes free, \
$jvmInfo.totalMemory bytes total
"""

def query = new ObjectName('oc4j:*')
String[] allNames = server.queryNames(query, null)
def dests = allNames.findAll{ name ->
name.contains('j2eeType=JMSDestinationResource')
}.collect{ new GroovyMBean(server, new ObjectName(it)) }

println "Found ${dests.size()} JMS destinations. Listing ..."
dests.each{ d -> println "$d.name: $d.location" }

Pasman has a blog

A work buddy of mine from Oracle Support has just setup a new blog. Judging from the types of questions he sends to me, he sees a lot of things that happen in the real world, so chances are he's going to have some interesting things to write about when time permits.

http://theblasfrompas.blogspot.com/

08 June 2007

admin_client.jar versus admin.jar

A pretty good question was asked recently on the OTN forum for OC4J about the two outwardly similar command line utilities we have in OC4J/OracleAS 10.1.3.x.

I thought I'd post the question and answer here in case anyone else was wondering about this.

Question:

OC4J provide two command-line utility: admin.jar and admin_client.jar for performing configuration tasks on OC4J.

admin_client.jar can work in Oracle Application Server clustered environment as well as on a standalone OC4J server, but admin.jar only works in a standalone OC4J installation.

Do they have any other difference?


If used in a standalone OC4J installation, are they the same?


Answer:

In a nutshell the situation is this:

admin.jar is the old command line utility we kept in the release for backwards compatibility. It uses a set of internal/proprietrary APIs to work against OC4J. It works only against OC4J standalone and does not allow you to perform any of its operations against an OracleAS instance.

admin_client.jar is a new command line utility added in 10.1.3.x that is based on JMX and its associated set of specifications to manage and deploy to a J2EE container. It can connect to and manage all variants of OC4J -- standalone, single AS instance, clustered AS instance. It has many more resource configuration options added to it.

For simplicity, we maintained the "feel" of admin.jar so the command structure is very similar to what was provided in admin.jar, but the way it works is completely different under the covers.

While admin.jar still works and is supported, you should use admin_client.jar whenever you want to do command line operations with 10.1.3.x. I was considering putting a note to that effect on stdout whenever admin.jar was used, but we didn't end up doing it.

Another two nice aspects about admin_client.jar are

1. The common library it uses to perform its operations is also used by the Oracle Ant tasks -- so in effect there are two faces to it -- admin_client.jar and Ant tasks. There's a near 100% compatibility between the two meaning what you can do with admin_client.jar you can most likely do with a set of Ant tasks from a development environment.

2. We have isolated the dependencies for admin_client.jar and produced a separate, small distribution called oc4j_admin_client.zip (which is 6MB from memory) that can be unzipped onto any remote server and you have the full admin_client.jar utility -- meaning that to perform administration options against ant of the OC4J variants (standalone, single and clustered AS instance) you only need this one, very small distribution.

The documentation on the OC4J management tools covers this to some degree:
http://download-west.oracle.com/docs/cd/B32110_01/web.1013/b28950/admin.htm#CEGHHGGB