Skip to main content

Using JMX and J2SE 5.0 to Securely Manage Web Applications

November 15, 2005

{cs.r.title}





















Contents
Setting Up the Sample Application
Tracking Some Meaningful Data
Creating JMX MBeans
Creating and Securing a JMX Agent
   Creating a MBeanServer with MBeans
   Creating the
JMXServiceURL
   Securing the Service
Starting the RMI Registry
   Using the Command Line
   Programmatically Starting the RMI Registry
Accessing our JMX Service
   Connecting using MC4J
   Connecting to a "Cluster" using jManage
Conclusion
Resources



JMX (Java Management Extensions) supplies tools for managing
local and remote applications, system objects, devices, and more.
This article will explain how to remotely manage a web application
using JMX (JSR
160
). It will explain the code needed inside of the application to
make it available to JMX clients and will demonstrate how to
connect to your JMX-enabled application using different clients
such as "http://mc4j.org/confluence/display/MC4J/Home">MC4J and
jManage. Securing the
communication layer using the RMI protocol and JNDI is also covered
in detail.


We will review a simple web application that monitors the number
of users that have logged in and exposes that statistic via a
secure JMX service. We will also run multiple instances of this
application and track statistics from all running instances. The
sample web application can be downloaded "/today/2005/11/15/jmxapp.zip">here. It requires you to have the "http://java.sun.com/j2se/1.5.0/download.jsp">J2SE 5.0 SDK
installed and your JAVA_HOME environment variable
pointing to the base installation directory. J2SE 5.0 implements
the JMX API, version 1.2, and the JMX Remote API, version 1.0. A
supporting servlet container is also required; I'm using
Apache Tomcat
5.5.12
. I'm also using Apache
Ant
to build the sample application.

Setting up the Sample Application

Download the sample application and
create a WAR file with ant war (for more
details, see the comments in build.xml). Copy jmxapp.war
to Tomcat's webapps directory. Assuming Tomcat is running on
your local machine on port 8080, the URL for the application will
be:

[prettify]
http://localhost:8080/jmxapp
[/prettify]

If you see a login screen that prompts you for your username
and password, all is well.

Tracking Some Meaningful Data

The sample application uses the "http://struts.apache.org/">Struts framework to submit the
login form. Upon submission, the
LoginAction.execute(..) method is executed, which quite
simply checks that the user ID is "hello" and the password is
"world." If both are true, then the login was successful and
control is forwarded to login_success.jsp; if not, then we
go back to the login form. Depending on whether the login was
successful or not, the
incrementSuccessLogins(HttpServletRequest) method or the
incrementFailedLogins(HttpServletRequest) method is
called. Let's have a look at
incrementFailedLogins(HttpServletRequest):

[prettify]
private void incrementFailedLogins
                    (HttpServletRequest request) {
    HttpSession session = request.getSession();
    ServletContext context =
                    session.getServletContext();
    Integer num = (Integer)
            context.getAttribute(
                Constants.FAILED_LOGINS_KEY);
    int newValue = 1;
    if (num != null) {
        newValue = num.intValue() + 1;
    }
    context.setAttribute(
            Constants.FAILED_LOGINS_KEY,
            new Integer(newValue));
}
[/prettify]

The method increments a FAILED_LOGINS_KEY variable
that is stored in application scope. The
incrementSuccessLogins(HttpServletRequest) method is
implemented in a similar way. The application now keeps track of
how many people successfully logged in and how many failed
authentication. That's great, but how do we access this data?
That's where JMX kicks in.

Creating JMX MBeans

"http://java.sun.com/j2se/1.5.0/docs/guide/management/overview.html">
The basics of MBeans
and where they fit into the JMX
architecture is beyond the scope of this article. We will simply
create, implement, expose, and secure an MBean for our application.
We are interested in exposing two pieces of data corresponding to
the following two methods. Here is the our simple MBean
interface:

[prettify]
public interface LoginStatsMBean {
    public int getFailedLogins();
    public int getSuccessLogins();
}
[/prettify]

Quite simply, the two methods return the number of failed and
successful logins. The LoginStatsMBean implementation,
LoginStats, provides a concrete implementation for
both methods. Let's have a look at the
getFailedLogins() implementation:

[prettify]
public int getFailedLogins() {
    ServletContext context =
                       Config.getServletContext();
    Integer val = (Integer)
                     context.getAttribute(
                     Constants.FAILED_LOGINS_KEY);
    return (val == null) ? 0 : val.intValue();
}
[/prettify]

The method returns a value stored in the
ServletContext. The getSuccessLogins()
method is implemented in a similar manner.

Creating and Securing a JMX Agent

The JMXAgent class that manages the JMX-related
aspects of the application has a few responsibilities:

  1. Create an MBeanServer.
  2. Register LoginStatsMBean with the
    MBeanServer.
  3. Create a JMXConnector, allowing remote clients
    to connect.

    • Involves use of JNDI.
    • Must also have an RMI registry running.
  4. Securing the JMXConnector using a username and
    password.
  5. Starting and stopping the JMXConnector on
    application start and stop, respectively.

The class outline for JMXAgent is:

[prettify]
public class JMXAgent {
    public JMXAgent() {
        // Initialize JMX server
    }
    public void start() {
        // Start JMX server
    }
    // called at application end
    public void stop() {
        // Stop JMX server
    }
}
[/prettify]

Let's understand the code in the constructor that will enable
clients to remotely monitor the application.

Creating a MBeanServer with MBeans

We first create a MBeanServer object, which is the
core component of the JMX infrastructure. It allows us to expose
our MBeans as manageable objects. The
MBeanServerFactory.createMBeanServer(String) method
makes this an easy task. The parameter supplied is the domain of
the server. Think of this as the unique name for this
MBeanServer. Next, we register the
LoginStatsMBean with the MBeanServer. The
MBeanServer.registerMBean(Object, ObjectName) method
takes in as a parameter an instance of the MBean implementation and
an object of type ObjectName that uniquely identifies
the MBean; in this case, DOMAIN + ":name=LoginStats"
suffices.

[prettify]
MBeanServer server =
     MBeanServerFactory.createMBeanServer(DOMAIN);
server.registerMBean(
    new LoginStats(),
    new ObjectName(DOMAIN + ":name=LoginStats"));
[/prettify]

Creating the JMXServiceURL

At this point, we have created an MBeanServer and
registered LoginStatsMBean with it. The next step is
to make the server available to clients. To do this, we must create
a JMXServiceURL, which represents the URL that clients
will use to access the JMX service:

[prettify]
JMXServiceURL url = new JMXServiceURL(
            "rmi",
            null,
            Constants.MBEAN_SERVER_PORT,
            "/jndi/rmi://localhost:" +
                Constants.RMI_REGISTRY_PORT +
                "/jmxapp");
[/prettify]

Let's look closely at the above line of code. The
JMXServiceURL constructor takes four arguments:

  1. The protocol to be used when connecting (rmi,
    jmxmp, iiop, etc.).
  2. The host machine of the JMX service. Supplying localhost as
    argument would also suffice however, supplying null
    forces the JMXServiceURL to find the best possible
    name for the host. For example, in this case, it would translate
    null to zarar, which is the name of my
    computer.
  3. The port used by the JMX service.
  4. Finally, we must supply the URL path that indicates how to
    find the JMX service. In this case, it would be
    /jndi/rmi://localhost:1099/jmxapp.

The URL path warrants more explanation:

[prettify]
/jndi/rmi://localhost:1099/jmxapp
[/prettify]

The /jndi part is saying that the client must do a
JNDI lookup for the JMX service. The
rmi://localhost:1099 indicates that there is an RMI
registry running on localhost at port 1099 (more on the RMI
registry later). The jmxapp is the unique identifier
of this JMX service in the RMI registry. A toString()
on the JMXServiceURL object yields the following:

[prettify]
service:jmx:rmi://zarar:9589/jndi/rmi://localhost:1100/jmxapp
[/prettify]

The above is the URL clients will eventually use to connect to
the JMX service. The J2SE 5.0 documentation has more on the

structure of this URL
.

Securing the Service

J2SE 5.0 provides a mechanism for JMX to authenticate users in
an easy manner. I have created a simple text file that stores
username and password information. The contents of the file
are:

[prettify]
zarar siddiqi
fyodor dostoevsky
[/prettify]

The users zarar and fyodor are
authenticated by the passwords siddiqi and
dostoevsky, respectively. The next step is to create
and secure a JMXConnectorServer that exposes the
MBeanServer. The path of the username/password file is
stored in a Map under the key,
jmx.remote.x.password.file. This Map is
later used when creating the JMXConnectorServer.

[prettify]
ServletContext context = Config.getServletContext();
// Get file which stores jmx user information
String userFile =  context.getRealPath("/") +
                "/WEB-INF/classes/" +
                Constants.JMX_USERS_FILE;
// Create authenticator and initialize RMI server
Map<string> env = new HashMap<string>();
env.put("jmx.remote.x.password.file", userFile);
[/prettify]

Now let's create the JMXConnectorServer. The
following line of code does the trick.

[prettify]
connectorServer = JMXConnectorServerFactory.
          newJMXConnectorServer(url, env, server);
[/prettify]

The

JMXConnectorServerFactory.newJMXConnectorServer(JMXServiceURL,
Map, MBeanServer)
method takes in as arguments three objects
we have just created: the JMXServiceURL, the
Map that stores authentication information, and the
MBeanServer. The connectorServer instance
variable allows us to start() and stop()
the JMXConnectorServer on application start and stop,
respectively.

Although the J2SE 5.0 implementation of JSR 160 is quite
powerful, other implementations, such as "http://mx4j.sourceforge.net/">MX4J, provide classes that offer
convenient features such as obfuscating of passwords, namely
the

"http://mx4j.sourceforge.net/docs/api/mx4j/tools/remote/PasswordAuthenticator.html">
PasswordAuthenticator
class.







Starting the RMI Registry

Earlier, I alluded to a RMI registry and said that a JNDI lookup
is done when accessing the service. However, right now we
don't have a RMI registry running, so a JNDI lookup will
fail. A RMI registry started may be started manually or
programmatically.

Using the Command Line

On your Windows or Linux command line, type the following to
start a RMI registry:

[prettify]
rmiregistry &
[/prettify]

This will start the RMI registry at your default host and port,
localhost and 1109, respectively. However, for our web application
we cannot rely on an RMI registry being available on application
start and would rather take care of this in our code.

Programmatically Starting the RMI Registry

To programmatically start the RMI Registry, you can use the
LocateRegistry.createRegistry(int port) method. The
method returns an object of the type Registry. We store
this reference, as we would like to stop the registry on application
end. Right before we start our JMXConnectorServer in
JMXAgent.start(), we first start an RMI registry using
the following line of code:

registry = LocateRegistry.createRegistry(
                    Constants.RMI_REGISTRY_PORT);

On application end, after stopping the
JMXConnectorServer in JMXAgent.stop(),
the following method is called to stop the registry:

UnicastRemoteObject.unexportObject(registry, true);

Note that the StartupListener class triggers
application start and end tasks.

Accessing our JMX Service

There are a number of ways we can access JSR 160 services. We
may do so programmatically or by using a GUI.

Connecting using MC4J

Deploy the application by copying jmxapp.war to Tomcat's
webapps directory. Download and install "http://mc4j.org/confluence/display/MC4J/Home">MC4J. Once
installed, create a new Server Connection of the type JSR 160 and
specify the Server URL that was printed in the application server
logs on application startup. In my case, it was:

[prettify]
    service:jmx:rmi://zarar:9589/jndi/rmi://localhost:1100/jmxapp
    
[/prettify]

Supply the username and password, which MC4J refers to as
"Principle" and "Credentials," respectively. Clicking Next takes
you to a screen where you can customize your classpath. Default
settings should work fine, and you can click on Finish to connect
to the JMX service. Once connected, browse the MC4J tree structure
as shown in Figure 1 until you reach the Properties option of the
LoginStats MBean implementation.

MC4J View
Figure 1. MC4J view

Clicking on the Properties option displays the statistics, as
shown in Figure 2:

Properties Window
Figure 2. Properties window

Connecting to a "Cluster" using jManage

Deploy the application by copying jmxapp.war to Tomcat's
webapps directory. Note the URL that is printed on
application startup. Next, deploy another instance of this
application by changing the RMI_REGISTRY_PORT and
MBEAN_SERVER_PORT variables in the
Constants class so that the second instance of the
application will not try to use ports that are already in use.
Change the app.name property in the build.xml
file so that the new instance will be deployed in a different
context (e.g., jmxapp2). Do an ant clean war, which will
create jmxapp2.war in the base directory. Copy
jmxapp2.war to Tomcat's webapps directory. The
application will deploy and now you have two instances of the same
application running. Again, note the URL that is printed on
startup.

Download and install "http://www.jmanage.org/">jManage. Once installed, use
jManage's web interface to create a JSR 160 application by
following the Add New Application link found on the home page.
The Add Application page is shown in Figure 3:

Add Application page
Figure 3. Add Application page

Once again, use the appropriate username, password, and URL.
Repeat the steps for the second application that is deployed. Once
you have created the two applications, you must create a cluster by
following the Add New Application Cluster link found on the home
page. Simply add the two applications you have already created to
your cluster, as shown in Figure 4:

Add Application Cluster page
Figure 4. Add Application Cluster page

That's it, we are done! From the home page, click on one of the
applications in the cluster and then click on the Find More
Objects button. You will see the name=LoginStats MBean; click on
it, and you will see the FailedLogins and
SuccessLogins attributes that we have exposed.
Clicking on the Cluster View link on the same page will display a
page similar to Figure 5, where a running count of statistics from
both applications can be seen:

<br "Cluster view for jmxapp and jmxapp2" />
Figure 5. Cluster view for jmxapp and jmxapp2

Try a few logins on both applications
(http://localhost:8080/jmxapp and
http://localhost:8080/jmxapp2) and see how the numbers
change.

Conclusion

You now know how to "JMX enable" your new and existing web
applications and securely manage them using MC4J and jManage.
Although J2SE 5.0 provides a powerful implementation of the JMX
specification, other open source projects such as "http://www.xmojo.org/products/xmojo/index.html">XMOJO and
MX4J provide additional
features, such as connecting via web interfaces and more. Interested
readers who want to learn more about JMX will find "http://www.oreilly.com/catalog/javamngext/index.html">Java
Management Extensions
by "http://www.oreillynet.com/cs/catalog/view/au/905">J. Steven
Perry
a very useful book. For those interested in remote
application management, Connecting JMX Clients and Servers
by Jeff Hanson is a valuable resource that provides real-world
examples.

Resources

width="1" height="1" border="0" alt=" " />
Zarar Siddiqi is an analyst for the University of Toronto.
Related Topics >> Programming   |   Security   |