| /* |
| * 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 |
| } |
| } |
| |
| } |