I'm monitoring my server with JMX remotely. However, the jmx-access and jmx-password store clear passwords which I do not want.
Followed with How to encrypt passwords for JConsole's password file, How to make the JMX custom authentication work? and How do I create a Login Module?, I wrote a custom login module.
My Login Module:
public class EncryptedFileLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
private String name;
private String password;
private boolean succeeded = false;
public EncryptedFileLoginModule() {
System.out.println("Login Module - constructor called");
}
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
System.out.println("Login Module - initialize called");
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
System.out.println("testOption value: " + (String) options.get("testOption"));
succeeded = false;
}
public boolean login() throws LoginException {
System.out.println("Login Module - login called");
if (callbackHandler == null) {
throw new LoginException("Oops, callbackHandler is null");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("name:");
callbacks[1] = new PasswordCallback("password:", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException e) {
throw new LoginException("Oops, IOException calling handle on callbackHandler");
} catch (UnsupportedCallbackException e) {
throw new LoginException("Oops, UnsupportedCallbackException calling handle on callbackHandler");
}
NameCallback nameCallback = (NameCallback) callbacks[0];
PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
name = nameCallback.getName();
password = new String(passwordCallback.getPassword());
if ("a".equals(name) && "a".equals(password)) {
System.out.println("Success! You get to log in!");
succeeded = true;
return succeeded;
} else {
System.out.println("Failure! You don't get to log in");
succeeded = false;
throw new FailedLoginException("Sorry! No login for you.");
}
}
public boolean abort() throws LoginException {
System.out.println("Login Module - abort called");
return false;
}
public boolean commit() throws LoginException {
System.out.println("Login Module - commit called");
return succeeded;
}
public boolean logout() throws LoginException {
System.out.println("Login Module - logout called");
return false;
}
}
class UserPrincipal implements Principal, Serializable {
private static final long serialVersionUID = -4604480892359393296L;
private String name;
public UserPrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
public boolean equals(Object o) {
return o instanceof UserPrincipal &&
((UserPrincipal)o).name.equals(name);
}
public int hashCode() {
return name.hashCode();
}
}
class StatePrincipal implements Principal, Serializable {
private static final long serialVersionUID = 8429580270033209093L;
private String state;
public StatePrincipal(String state) {
this.state = state;
}
public String getName() {
return state;
}
public String toString() {
return state;
}
public boolean equals(Object o) {
return o instanceof StatePrincipal && ((StatePrincipal)o).equals(state);
}
public int hashCode() {
return state.hashCode();
}
}
And here is my Login Module config file (d:/mysecurity.cfg):
MyLoginModule {
cn.com.singlee.slice.security.EncryptedFileLoginModule REQUIRED
testOption=here_is_an_option;
};
When starting my server, I use these parameters:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.ssl=false
-Djava.security.auth.login.config=d:/mysecurity.cfg
-Dcom.sun.management.jmxremote.login.config=MyLoginModule
However, when I try to connect the server with JCsonle, I cannot login. The server seems to be able to invoke the custom authentication procudure successfully with these log output:
Login Module - constructor called
Login Module - initialize called
testOption value: here_is_an_option
Login Module - login called
Success! You get to log in!
Login Module - commit called
But JConsole reports errors (I started JConsole in debug mode with "jconsole -debug"):
java.lang.SecurityException: Access denied! No entries found in the access file [C:\Program Files\Java\jre7\lib\management\jmxremote.access] for any of the authenticated identities []
at sun.management.jmxremote.ConnectorBootstrap$AccessFileCheckerAuthenticator.checkAccessFileEntries(Unknown Source)
at sun.management.jmxremote.ConnectorBootstrap$AccessFileCheckerAuthenticator.authenticate(Unknown Source)
at javax.management.remote.rmi.RMIServerImpl.doNewClient(Unknown Source)
at javax.management.remote.rmi.RMIServerImpl.newClient(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
at javax.management.remote.rmi.RMIServerImpl_Stub.newClient(Unknown Source)
at javax.management.remote.rmi.RMIConnector.getConnection(RMIConnector.java:2370)
at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:296)
at sun.tools.jconsole.ProxyClient.tryConnect(ProxyClient.java:366)
at sun.tools.jconsole.ProxyClient.connect(ProxyClient.java:314)
at sun.tools.jconsole.VMPanel$2.run(VMPanel.java:295)
This error complains that no authenticated identities were found in jmxremote.access.
Since I use a custom authentication login module, why is jmxremote.access needed?
What's more, even if I add the test user "a" in jmxremote.access, the error still exists.
Any clue?
Thank you for this post the jconsole -debug was the break through
I believe what you are missing is that you have not configured "a" in the jmxremote.access, Using your above example, when i changed a few lines it worked for me
note the addition of the
user = new JMXPrincipal(name);
and using the uncommented lines in "jmxremote.access"
controlRole readwrite
I hope this helps other programmers in the same situation.
public class EncryptedFileLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
private JMXPrincipal user;
private String name;
private String password;
private boolean succeeded = false;
public EncryptedFileLoginModule() {
System.out.println("Login Module - constructor called");
}
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
System.out.println("Login Module - initialize called");
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
System.out.println("testOption value: " + (String) options.get("testOption"));
succeeded = false;
}
public boolean login() throws LoginException {
System.out.println("Login Module - login called");
if (callbackHandler == null) {
throw new LoginException("Oops, callbackHandler is null");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("name:");
callbacks[1] = new PasswordCallback("password:", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException e) {
throw new LoginException("Oops, IOException calling handle on callbackHandler");
} catch (UnsupportedCallbackException e) {
throw new LoginException("Oops, UnsupportedCallbackException calling handle on callbackHandler");
}
NameCallback nameCallback = (NameCallback) callbacks[0];
PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
name = nameCallback.getName();
password = new String(passwordCallback.getPassword());
if ("controlRole".equals(name) && "a".equals(password)) {
System.out.println("Success! You get to log in!");
// Create a new user principal
user = new JMXPrincipal(name);
succeeded = true;
return succeeded;
} else {
System.out.println("Failure! You don't get to log in");
succeeded = false;
throw new FailedLoginException("Sorry! No login for you.");
}
}
public boolean abort() throws LoginException {
System.out.println("Login Module - abort called");
return false;
}
public boolean commit() throws LoginException {
System.out.println("Login Module - commit called");
subject.getPrincipals().add(user);
return succeeded;
}
public boolean logout() throws LoginException {
System.out.println("Login Module - logout called");
user = null;
succeeded = false;
return false;
}
}
class UserPrincipal implements Principal, Serializable {
private static final long serialVersionUID = -4604480892359393296L;
private String name;
public UserPrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
public boolean equals(Object o) {
return o instanceof UserPrincipal &&
((UserPrincipal)o).name.equals(name);
}
public int hashCode() {
return name.hashCode();
}
}
class StatePrincipal implements Principal, Serializable {
private static final long serialVersionUID = 8429580270033209093L;
private String state;
public StatePrincipal(String state) {
this.state = state;
}
public String getName() {
return state;
}
public String toString() {
return state;
}
public boolean equals(Object o) {
return o instanceof StatePrincipal && ((StatePrincipal)o).equals(state);
}
public int hashCode() {
return state.hashCode();
}
}