/* |
|
* 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; |
|
/** |
|
* <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 <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 <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). |
|
*/ |
|
private final class JMXCallbackHandler implements CallbackHandler { |
|
/** |
|
* 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: |
|
* <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 <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 |
|
*/ |
|
public AppConfigurationEntry[] getAppConfigurationEntry(String name) { |
|
return name.equals(LOGIN_CONFIG_NAME) ? entries : null; |
|
} |
|
/** |
|
* Refreshes the configuration. |
|
*/ |
|
public void refresh() { |
|
// the configuration is fixed |
|
} |
|
} |
|
} |