JSR 160 provides a pluggable authentication mechanism based on the
javax.management.remote.JMXAuthenticator interface.
The JMXAuthenticator interface has only one method that takes credentials from the client and returns a
javax.security.auth.Subject:
public Subject authenticate(Object credentials) throws SecurityException
An implementation of that interface may read username / password pairs from a file, and compare them with the supplied credentials (for example a String[2] where String[0] contains the username and String[1] contains the password).
MX4J provides a default implementation of the JMXAuthenticator interface, namely the
mx4j.tools.remote.PasswordAuthenticator class. Refer to the PasswordAuthenticator
javadoc for details.
It is possible to specify a file or a resource containing username/password pairs (where
passwords can be in clear text or obfuscated via a digest algorithm) to the PasswordAuthenticator, that
will then check correctness of the credentials passed by the client against this file or resource.
It is worth noting that JMXConnectorServers that offer a strong security (an example of a JMXConnectorServer that offers strong security is the - optional - javax.management.remote.jmxmp.JMXMPConnectorServer, whose security is based on SASL) may choose not to base their security system on JMXAuthenticator.
The JMXAuthenticator implementation must be provided to the JMXConnectorServer via the environment map:
Example 3.6. Using JMXAuthenticator in a JMXConnectorServer
// The address of the connector server JMXServiceURL address = ...; // The JMXAuthenticator implementation JMXAuthenticator authenticator = ...; // The environment map Map environment = new HashMap(); environment.put(JMXConnectorServer.AUTHENTICATOR, authenticator); // The MBeanServer to which the JMXConnectorServer will be attached to MBeanServer server = MBeanServerFactory.createMBeanServer(); // Create the JMXCconnectorServer JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, environment, server); // Start the JMXConnectorServer cntorServer.start();
Once the JMXConnectorServer is setup correctly, it is possible to connect only by providing correct credentials
from a JMXConnector.
The credentials must be serializable because they are provided by the JMXConnector and will be sent to the
JMXConnectorServer to authenticate a Subject.
If the credentials provided are not correct, a SecurityException is raised.
Example 3.7. Passing credentials using a JMXConnector
// The address of the connector server JMXServiceURL address = ...; // Create the JMXCconnectorServer JMXConnector cntor = JMXConnectorFactory.newJMXConnector(address, null); // The credentials Object credentials = ...; // The connection environment map Map environment = new HashMap(); environment.put(JMXConnector.CREDENTIALS, credentials); // Connect and invoke an operation on the remote MBeanServer try { cntor.connect(environment); } catch (SecurityException x) { // Uh-oh ! Bad credentials ! throw x; } // Obtain a stub for the remote MBeanServer MBeanServerConnection mbsc = cntor.getMBeanServerConnection(); // Call the remote MBeanServer ObjectName delegate = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate"); String id = mbsc.getAttribute(delegate, "MBeanServerId");
Once correct credentials are provided the server side, the JMXAuthenticator can authenticate a Subject.
When you invoke operations on the MBeanServerConnection object obtained from the JMXConnector, the invocation
end up on the server side, and over there the operation will be executed on behalf of the authenticated Subject.
If a SecurityManager is in place, you can specify different permissions for different authenticated subjects,
fine tuning the security policy as you want.
You can for example grant all permissions to an "admin" principal, while granting only read access to MBean
attributes to a "guest" principal.
If a SecurityManager is not in place, then using a JMXAuthenticator only allows you to deny access to non authenticated users: a simple way to restrict the access to the JMXConnectorServer.
Running a JMXConnectorServer under a SecurityManager allows to fine tune access to it from remote clients.
If you just want to restrict access to the JMXConnectorServer to authenticated users, but allow an authenticated user to perform any operation on the JMXConnectorServer and its underlying MBeanServer, then it is enough to provide a JMXAuthenticator while creating the JMXConnectorServer, as explained in the section above.
If instead, you want to be able to grant different permissions to different users, then you have to
run the JMXConnectorServer under a SecurityManager and grant different permissions to different users.
For example, you may want the "admin" user to be able to perform any operation on the remote MBeanServer,
and the "guest" user to be able to perform only read operations, and not write operations.
To show how to achieve this fine tuning, we will refer to example classes bundled with the MX4J distribution,
namely
mx4j.examples.remote.security.Client and
mx4j.examples.remote.security.Server, and show how to setup the policy files in JDK 1.4
and JDK 1.3.
By default it will shown the sequence of operations for JDK 1.4, and differences with JDK 1.3 will be
highlighted.
We will assume that the JMX implementation (mx4j.jar), the JMX Remote implementation (mx4j-remote.jar)
and the MX4J examples (mx4j-examples.jar) are present in a directory called $WORK/lib, and that the
example is started from the $WORK directory.
For JDK 1.3 you will also need jaas.jar in the $WORK/lib directory.
As first step, let's take a look at
mx4j.examples.remote.security.Server.
It first creates a "users.properties" file with the "admin" and "guest" users under $WORK.
This step is of course omitted in a real environment, where the "users.properties" file is provided
externally.
Then it starts the NamingService and the JMXConnectorServer over RMI/JRMP, specifying a JMXAuthenticator
that reads user names and passwords from the "users.properties" file.
Let's create now a policy file under $WORK, server.policy:
Example 3.8. The server.policy file
grant codebase "file:${user.dir}/lib/mx4j-remote.jar" { permission java.security.AllPermission; }; grant codebase "file:${user.dir}/lib/mx4j.jar" { permission java.security.AllPermission; }; grant codebase "file:${user.dir}/lib/mx4j-tools.jar" { permission java.security.AllPermission; }; /* Uncomment for JDK 1.3 grant codebase "file:${user.dir}/lib/jaas.jar" { permission java.security.AllPermission; }; */ grant codebase "file:${user.dir}/lib/mx4j-examples.jar" { permission java.security.AllPermission; };
Notice how we grant AllPermission to the MX4J jars that implement JMX and JMX Remote,
and to the MX4J Tools jar (and to JAAS jar for JDK 1.3).
You can consider those jars as libraries, and you trust that the MX4J implementation, as well as the JAAS
implementation, won't harm your system.
Different is the motivation behind granting AllPermission to the MX4J Examples jar, which is the one that
containes
mx4j.examples.remote.security.Server.
For this example we grant AllPermission because we trust the Server class not to perform any security
sensitive operation beyond starting the JMXConnectorServer and reading the "users.properties" file,
but you may want to restrict the set of operations performed by the classes that start the
JMXConnectorServer.
With the above server.policy file, you can successfully start the
mx4j.examples.remote.security.Server class, in this way:
Example 3.9. Starting the secured server
$WORK>java -Djava.security.manager -Djava.security.policy==server.policy -Djava.security.auth.policy=jaas.server.policy --> Only for JDK 1.3 -classpath lib/mx4j-examples.jar; \ lib/mx4j.jar; \ lib/mx4j-remote.jar \ lib/jaas.jar --> Only for JDK 1.3 mx4j.examples.remote.security.Server
Note the double equals sign '=' after the property "java.security.policy": see the documentation guide about policy files for the JDK for further information about the meaning of this double equals sign.
It is time now to grant permissions for the operations a remote client that is connecting
may want to do.
Since we specified a JMXAuthenticator, only authenticated users can connect to the JMXConnectorServer.
Furthermore, since we run under a SecurityManager, authenticated users may only perform the operations
granted to the corrispondent principal in the policy file.
For JDK 1.3, these permissions must be specified in a separate file, named here "jaas.policy.file",
referenced by the "java.security.auth.policy" system property at startup time (see above).
For JDK 1.4, these permissions can be added to the "server.policy" file.
Example 3.10. The principal-specific permissions
grant { // Required by MX4J implementation permission javax.security.auth.AuthPermission "doAsPrivileged"; // Required to all clients to connect to the server permission java.net.SocketPermission "localhost:1024-", "listen,resolve"; permission java.net.SocketPermission "*", "accept,resolve"; }; grant principal javax.management.remote.JMXPrincipal "admin" { permission java.security.AllPermission; }; grant principal javax.management.remote.JMXPrincipal "guest" { permission javax.management.MBeanPermission "*", "queryNames"; permission javax.management.remote.SubjectDelegationPermission "javax.management.remote.JMXPrincipal.*"; };
First of all, note that there is no specification of the codebase.
It is not possible to specify the codebase for permissions you want to grant to a JSR 160 connector
server, you have to omit it.
Second, note that there are three grant blocks: one that specifies permissions common to all principals, one that specifies permissions for the "admin" principal, and one that specifies permissions for the "guest" principal.
The common block allows anyone to connect to the server side, as specified by the two SocketPermissions.
The "admin" principal has AllPermission, while the "guest" principal can only call the javax.management.MBeanServerConnection.queryNames() method.
In the same exact way, you can specify many principal blocks, one per principal, with a different set of permission for each one; this allows to fine tune the permissions granted to each principal that connects to the server side.
Lastly, note the SubjectDelegationPermission. This permission is needed when you have, on client, a
JAAS Subject and you want to perform, on server side, operations on behalf of that Subject.
For example, it is possible that on a client there are many users that want to interact with the
server side, and that all users have been granted the same set of permissions.
In this case is more efficient to establish only one connection with the server side, with an
authenticated user (in the example above, "guest"), and then use that connection with many
delegate users, by using
javax.management.remote.JMXConnector.getMBeanServerConnection(Subject delegate).
In the example above, the authenticated principal "guest" allows any delegate principal (of class
javax.management.remote.JMXPrincipal) to perform
operations in its behalf, since it grants a SubjectDelegationPermission with a name of
"javax.management.remote.JMXPrincipal.*" (refer to the JMX Remote javadocs for explanation of the
syntax of SubjectDelegationPermission).