In addition to the simple SASL security and the privacy-based security you can apply to JMXMP connectors, you can also define a custom SASL provider mechanism by defining your own SASL Provider.
The SASL mechanism used in this example is a straightforward customized mechanism called SAMPLE SASL. The example is found in the subdirectories of examplesDir/current/Security/jmxmp/sasl_provider. The SAMPLE SASL mechanism is defined in the SampleServer class, shown below.
import java.io.*; import javax.security.sasl.*; public class SampleServer implements SaslServer { private String username; private boolean completed = false; public String getMechanismName() { return "SAMPLE"; } public byte[] evaluateResponse(byte[] responseData) throws SaslException { if (completed) { throw new SaslException("Already completed"); } completed = true; username = new String(responseData); return null; } public String getAuthorizationID() { return username; } public boolean isComplete() { return completed; } public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { throw new SaslException("No negotiated security layer"); } public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { throw new SaslException("No negotiated security layer"); } public Object getNegotiatedProperty(String propName) { if (propName.equals(Sasl.QOP)) { return "auth"; } return null; } public void dispose() throws SaslException { } }
The custom SASL SAMPLE mechanism defined by the SampleServer class, shown above, extends the SASL interface SaslServer. It simply calls the standard SaslServer methods one by one, to perform the following security operations:
A call to getMechanismName establishes that the SASL mechanism used must be SAMPLE.
Any response from the client end of the connection is evaluated by evaluateResponse, and an appropriate challenge to this response is generated.
The authorization ID for this connection is built from the client's user name.
public class SampleClient implements SaslClient { private byte[] username; private boolean completed = false; public SampleClient(String authorizationID) throws SaslException { if (authorizationID != null) { try { username = ((String)authorizationID).getBytes("UTF8"); } catch (UnsupportedEncodingException e) { throw new SaslException("Cannot convert " + authorizationID + " into UTF-8", e); } } else { username = new byte[0]; } } public String getMechanismName() { return "SAMPLE"; } public boolean hasInitialResponse() { return true; } public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { if (completed) { throw new SaslException("Already completed"); } completed = true; return username; } public boolean isComplete() { return completed; } public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { throw new SaslException("No negotiated security layer"); } public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { throw new SaslException("No negotiated security layer"); } public Object getNegotiatedProperty(String propName) { if (propName.equals(Sasl.QOP)) { return "auth"; } return null; } public void dispose() throws SaslException { } }
The SampleClient class is an extension of the SaslClient interface, and calls the standard SaslClient methods one by one, to perform the following operations that are expected by the SampleServer:
Defines a username, based on the authorization ID granted by the server.
A call to getMechanismName establishes that the SASL mechanism used must be SAMPLE.
Establishes that this SASL client implements the initial response mechanism expected by the server.
Evaluates a challenge send by the server to the client following the client's initial response, and replies accordingly. If the client can provide the correct user name, then the isComplete method returns true, and the connection can proceed.
With these SASL server and client mechanisms defined, they can then be instantiated by their respective factories.
public class ServerFactory implements SaslServerFactory { public SaslServer createSaslServer(String mechs, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException { if (mechs.equals("SAMPLE")) { return new SampleServer(); } return null; } public String[] getMechanismNames(Map props) { return new String[]{"SAMPLE"}; } }
This basic implementation of the SaslServerFactory interface creates SampleServer instances when it is called with the SASL mechanism parameter set to SAMPLE. None of the other parameters are used by this mechanism.
The SampleClientFactory creates SampleClient instances in exactly the same way as the SampleServerFactory creates SampleServer instances.
public final class Provider extends java.security.Provider { public Provider() { super("SampleSasl", 1.0, "SAMPLE SASL MECHANISM PROVIDER"); put("SaslClientFactory.SAMPLE", "ClientFactory"); put("SaslServerFactory.SAMPLE", "ServerFactory"); } }
The SASL SAMPLE Provider constructor shown above specifies the name of the provider (SampleSasl), the version of this provider (in this case, 1.0) and a description of the provider. It then defines the server and client factories that are used to create SASL servers and clients implementing this SASL mechanism.
With the mechanisms above thus defined, all the Server and Client classes used in this example require the correct Provider to be installed in the environment.
public class Server { public static void main(String[] args) { try { MBeanServer mbs = MBeanServerFactory.createMBeanServer(); HashMap env = new HashMap(); Security.addProvider(new Provider()); env.put("jmx.remote.profiles", "SASL/SAMPLE"); env.put("jmx.remote.x.access.file", "config" + File.separator + "access.properties"); JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); cs.start(); } catch (Exception e) { e.printStackTrace(); } } }
The SAMPLE SASL mechanism is passed into the Client in the same way.
Run this example from within the examplesDir/current/Security/jmxmp/sasl_provider directory.
Compile the Java classes.
$ javac -classpath classpath \ mbeans/SimpleStandard.java \ mbeans/SimpleStandardMBean.java \ server/Server.java \ client/Client.java \ client/ClientListener.java \ sample/Provider.java \ sample/ClientFactory.java \ sample/ServerFactory.java \ sample/SampleClient.java \ sample/SampleServer.java |
Start the Server.
$ java -classpath server:sample:mbeans:classpath Server & |
You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.
Start the Client.
$ java -classpath client:sample:mbeans:classpath Client |
You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.