/*
* Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jmx.remote.security;
import java.io.IOException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXAuthenticator;
import javax.security.auth.AuthPermission;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;
This class represents a
JAAS based implementation of the JMXAuthenticator
interface.
Authentication is performed by passing the supplied user's credentials to one or more authentication mechanisms (LoginModule
) for verification. An authentication mechanism acquires the user's credentials by calling NameCallback
and/or PasswordCallback
. If authentication is successful then an authenticated Subject
filled in with a Principal
is returned. Authorization checks will then be performed based on this Subject
.
By default, a single file-based authentication mechanism FileLoginModule
is configured (FileLoginConfig
).
To override the default configuration use the
com.sun.management.jmxremote.login.config
management property described in the JRE/lib/management/management.properties file. Set this property to the name of a JAAS configuration entry and ensure that the entry is loaded by the installed Configuration
. In addition, ensure that the authentication mechanisms specified in the entry acquire the user's credentials by calling NameCallback
and PasswordCallback
and that they return a Subject
filled-in with a Principal
, for those users that are successfully authenticated.
/**
* <p>This class represents a
* <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a>
* based implementation of the {@link JMXAuthenticator} interface.</p>
*
* <p>Authentication is performed by passing the supplied user's credentials
* to one or more authentication mechanisms ({@link LoginModule}) for
* verification. An authentication mechanism acquires the user's credentials
* by calling {@link NameCallback} and/or {@link PasswordCallback}.
* If authentication is successful then an authenticated {@link Subject}
* filled in with a {@link Principal} is returned. Authorization checks
* will then be performed based on this <code>Subject</code>.</p>
*
* <p>By default, a single file-based authentication mechanism
* {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p>
*
* <p>To override the default configuration use the
* <code>com.sun.management.jmxremote.login.config</code> management property
* described in the JRE/lib/management/management.properties file.
* Set this property to the name of a JAAS configuration entry and ensure that
* the entry is loaded by the installed {@link Configuration}. In addition,
* ensure that the authentication mechanisms specified in the entry acquire
* the user's credentials by calling {@link NameCallback} and
* {@link PasswordCallback} and that they return a {@link Subject} filled-in
* with a {@link Principal}, for those users that are successfully
* authenticated.</p>
*/
public final class JMXPluggableAuthenticator implements JMXAuthenticator {
Creates an instance of JMXPluggableAuthenticator
and initializes it with a LoginContext
. Params: - env – the environment containing configuration properties for the
authenticator. Can be null, which is equivalent to an empty
Map.
Throws: - SecurityException – if the authentication mechanism cannot be
initialized.
/**
* Creates an instance of <code>JMXPluggableAuthenticator</code>
* and initializes it with a {@link LoginContext}.
*
* @param env the environment containing configuration properties for the
* authenticator. Can be null, which is equivalent to an empty
* Map.
* @exception SecurityException if the authentication mechanism cannot be
* initialized.
*/
public JMXPluggableAuthenticator(Map<?, ?> env) {
String loginConfigName = null;
String passwordFile = null;
if (env != null) {
loginConfigName = (String) env.get(LOGIN_CONFIG_PROP);
passwordFile = (String) env.get(PASSWORD_FILE_PROP);
}
try {
if (loginConfigName != null) {
// use the supplied JAAS login configuration
loginContext =
new LoginContext(loginConfigName, new JMXCallbackHandler());
} else {
// use the default JAAS login configuration (file-based)
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new AuthPermission("createLoginContext." +
LOGIN_CONFIG_NAME));
}
final String pf = passwordFile;
try {
loginContext = AccessController.doPrivileged(
new PrivilegedExceptionAction<LoginContext>() {
public LoginContext run() throws LoginException {
return new LoginContext(
LOGIN_CONFIG_NAME,
null,
new JMXCallbackHandler(),
new FileLoginConfig(pf));
}
});
} catch (PrivilegedActionException pae) {
throw (LoginException) pae.getException();
}
}
} catch (LoginException le) {
authenticationFailure("authenticate", le);
} catch (SecurityException se) {
authenticationFailure("authenticate", se);
}
}
Authenticate the MBeanServerConnection
client
with the given client credentials.
Params: - credentials – the user-defined credentials to be passed in
to the server in order to authenticate the user before creating
the
MBeanServerConnection
. This parameter must
be a two-element String[]
containing the client's
username and password in that order.
Throws: - SecurityException – if the server cannot authenticate the user
with the provided credentials.
Returns: the authenticated subject containing a
JMXPrincipal(username)
.
/**
* Authenticate the <code>MBeanServerConnection</code> client
* with the given client credentials.
*
* @param credentials the user-defined credentials to be passed in
* to the server in order to authenticate the user before creating
* the <code>MBeanServerConnection</code>. This parameter must
* be a two-element <code>String[]</code> containing the client's
* username and password in that order.
*
* @return the authenticated subject containing a
* <code>JMXPrincipal(username)</code>.
*
* @exception SecurityException if the server cannot authenticate the user
* with the provided credentials.
*/
public Subject authenticate(Object credentials) {
// Verify that credentials is of type String[].
//
if (!(credentials instanceof String[])) {
// Special case for null so we get a more informative message
if (credentials == null)
authenticationFailure("authenticate", "Credentials required");
final String message =
"Credentials should be String[] instead of " +
credentials.getClass().getName();
authenticationFailure("authenticate", message);
}
// Verify that the array contains two elements.
//
final String[] aCredentials = (String[]) credentials;
if (aCredentials.length != 2) {
final String message =
"Credentials should have 2 elements not " +
aCredentials.length;
authenticationFailure("authenticate", message);
}
// Verify that username exists and the associated
// password matches the one supplied by the client.
//
username = aCredentials[0];
password = aCredentials[1];
if (username == null || password == null) {
final String message = "Username or password is null";
authenticationFailure("authenticate", message);
}
// Perform authentication
try {
loginContext.login();
final Subject subject = loginContext.getSubject();
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
subject.setReadOnly();
return null;
}
});
return subject;
} catch (LoginException le) {
authenticationFailure("authenticate", le);
}
return null;
}
private static void authenticationFailure(String method, String message)
throws SecurityException {
final String msg = "Authentication failed! " + message;
final SecurityException e = new SecurityException(msg);
logException(method, msg, e);
throw e;
}
private static void authenticationFailure(String method,
Exception exception)
throws SecurityException {
String msg;
SecurityException se;
if (exception instanceof SecurityException) {
msg = exception.getMessage();
se = (SecurityException) exception;
} else {
msg = "Authentication failed! " + exception.getMessage();
final SecurityException e = new SecurityException(msg);
EnvHelp.initCause(e, exception);
se = e;
}
logException(method, msg, se);
throw se;
}
private static void logException(String method,
String message,
Exception e) {
if (logger.traceOn()) {
logger.trace(method, message);
}
if (logger.debugOn()) {
logger.debug(method, e);
}
}
private LoginContext loginContext;
private String username;
private String password;
private static final String LOGIN_CONFIG_PROP =
"jmx.remote.x.login.config";
private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator";
private static final String PASSWORD_FILE_PROP =
"jmx.remote.x.password.file";
private static final ClassLogger logger =
new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME);
This callback handler supplies the username and password (which was
originally supplied by the JMX user) to the JAAS login module performing
the authentication. No interactive user prompting is required because the
credentials are already available to this class (via its enclosing class).
/**
* This callback handler supplies the username and password (which was
* originally supplied by the JMX user) to the JAAS login module performing
* the authentication. No interactive user prompting is required because the
* credentials are already available to this class (via its enclosing class).
*/
private final class JMXCallbackHandler implements CallbackHandler {
Sets the username and password in the appropriate Callback object.
/**
* Sets the username and password in the appropriate Callback object.
*/
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback)callbacks[i]).setName(username);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback)callbacks[i])
.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
This class defines the JAAS configuration for file-based authentication.
It is equivalent to the following textual configuration entry:
JMXPluggableAuthenticator {
com.sun.jmx.remote.security.FileLoginModule required;
};
/**
* This class defines the JAAS configuration for file-based authentication.
* It is equivalent to the following textual configuration entry:
* <pre>
* JMXPluggableAuthenticator {
* com.sun.jmx.remote.security.FileLoginModule required;
* };
* </pre>
*/
private static class FileLoginConfig extends Configuration {
// The JAAS configuration for file-based authentication
private AppConfigurationEntry[] entries;
// The classname of the login module for file-based authentication
private static final String FILE_LOGIN_MODULE =
FileLoginModule.class.getName();
// The option that identifies the password file to use
private static final String PASSWORD_FILE_OPTION = "passwordFile";
Creates an instance of FileLoginConfig
Params: - passwordFile – A filepath that identifies the password file to use.
If null then the default password file is used.
/**
* Creates an instance of <code>FileLoginConfig</code>
*
* @param passwordFile A filepath that identifies the password file to use.
* If null then the default password file is used.
*/
public FileLoginConfig(String passwordFile) {
Map<String, String> options;
if (passwordFile != null) {
options = new HashMap<String, String>(1);
options.put(PASSWORD_FILE_OPTION, passwordFile);
} else {
options = Collections.emptyMap();
}
entries = new AppConfigurationEntry[] {
new AppConfigurationEntry(FILE_LOGIN_MODULE,
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options)
};
}
Gets the JAAS configuration for file-based authentication
/**
* Gets the JAAS configuration for file-based authentication
*/
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return name.equals(LOGIN_CONFIG_NAME) ? entries : null;
}
Refreshes the configuration.
/**
* Refreshes the configuration.
*/
public void refresh() {
// the configuration is fixed
}
}
}