346 lines · 13.1 KB · Java
RawDownload
/* * 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    }} }