1. /*
  2. * @(#)JMXPluggableAuthenticator.java 1.4 04/05/27
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.jmx.remote.security;
  8. import java.io.IOException;
  9. import java.security.AccessController;
  10. import java.security.Principal;
  11. import java.security.PrivilegedAction;
  12. import java.security.PrivilegedActionException;
  13. import java.security.PrivilegedExceptionAction;
  14. import java.util.Collections;
  15. import java.util.HashMap;
  16. import java.util.Map;
  17. import java.util.Properties;
  18. import javax.management.remote.JMXPrincipal;
  19. import javax.management.remote.JMXAuthenticator;
  20. import javax.security.auth.AuthPermission;
  21. import javax.security.auth.Subject;
  22. import javax.security.auth.callback.*;
  23. import javax.security.auth.login.AppConfigurationEntry;
  24. import javax.security.auth.login.Configuration;
  25. import javax.security.auth.login.LoginContext;
  26. import javax.security.auth.login.LoginException;
  27. import javax.security.auth.spi.LoginModule;
  28. import com.sun.jmx.remote.util.ClassLogger;
  29. import com.sun.jmx.remote.util.EnvHelp;
  30. /**
  31. * <p>This class represents a
  32. * <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a>
  33. * based implementation of the {@link JMXAuthenticator} interface.</p>
  34. *
  35. * <p>Authentication is performed by passing the supplied user's credentials
  36. * to one or more authentication mechanisms ({@link LoginModule}) for
  37. * verification. An authentication mechanism acquires the user's credentials
  38. * by calling {@link NameCallback} and/or {@link PasswordCallback}.
  39. * If authentication is successful then an authenticated {@link Subject}
  40. * filled in with a {@link Principal} is returned. Authorization checks
  41. * will then be performed based on this <code>Subject</code>.</p>
  42. *
  43. * <p>By default, a single file-based authentication mechanism
  44. * {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p>
  45. *
  46. * <p>To override the default configuration use the
  47. * <code>com.sun.management.jmxremote.login.config</code> management property
  48. * described in the JRE/lib/management/management.properties file.
  49. * Set this property to the name of a JAAS configuration entry and ensure that
  50. * the entry is loaded by the installed {@link Configuration}. In addition,
  51. * ensure that the authentication mechanisms specified in the entry acquire
  52. * the user's credentials by calling {@link NameCallback} and
  53. * {@link PasswordCallback} and that they return a {@link Subject} filled-in
  54. * with a {@link Principal}, for those users that are successfully
  55. * authenticated.</p>
  56. */
  57. public final class JMXPluggableAuthenticator implements JMXAuthenticator {
  58. /**
  59. * Creates an instance of <code>JMXPluggableAuthenticator</code>
  60. * and initializes it with a {@link LoginContext}.
  61. *
  62. * @param env the environment containing configuration properties for the
  63. * authenticator. Can be null, which is equivalent to an empty
  64. * Map.
  65. * @exception SecurityException if the authentication mechanism cannot be
  66. * initialized.
  67. */
  68. public JMXPluggableAuthenticator(Map env) {
  69. String loginConfigName = null;
  70. String passwordFile = null;
  71. if (env != null) {
  72. loginConfigName = (String) env.get(LOGIN_CONFIG_PROP);
  73. passwordFile = (String) env.get(PASSWORD_FILE_PROP);
  74. }
  75. try {
  76. if (loginConfigName != null) {
  77. // use the supplied JAAS login configuration
  78. loginContext =
  79. new LoginContext(loginConfigName, new JMXCallbackHandler());
  80. } else {
  81. // use the default JAAS login configuration (file-based)
  82. SecurityManager sm = System.getSecurityManager();
  83. if (sm != null) {
  84. sm.checkPermission(
  85. new AuthPermission("createLoginContext." +
  86. LOGIN_CONFIG_NAME));
  87. }
  88. final String pf = passwordFile;
  89. try {
  90. loginContext = (LoginContext) AccessController.doPrivileged(
  91. new PrivilegedExceptionAction() {
  92. public Object run() throws LoginException {
  93. return new LoginContext(
  94. LOGIN_CONFIG_NAME,
  95. null,
  96. new JMXCallbackHandler(),
  97. new FileLoginConfig(pf));
  98. }
  99. });
  100. } catch (PrivilegedActionException pae) {
  101. throw (LoginException) pae.getException();
  102. }
  103. }
  104. } catch (LoginException le) {
  105. authenticationFailure("authenticate", le);
  106. } catch (SecurityException se) {
  107. authenticationFailure("authenticate", se);
  108. }
  109. }
  110. /**
  111. * Authenticate the <code>MBeanServerConnection</code> client
  112. * with the given client credentials.
  113. *
  114. * @param credentials the user-defined credentials to be passed in
  115. * to the server in order to authenticate the user before creating
  116. * the <code>MBeanServerConnection</code>. This parameter must
  117. * be a two-element <code>String[]</code> containing the client's
  118. * username and password in that order.
  119. *
  120. * @return the authenticated subject containing a
  121. * <code>JMXPrincipal(username)</code>.
  122. *
  123. * @exception SecurityException if the server cannot authenticate the user
  124. * with the provided credentials.
  125. */
  126. public Subject authenticate(Object credentials) {
  127. // Verify that credentials is of type String[].
  128. //
  129. if (!(credentials instanceof String[])) {
  130. // Special case for null so we get a more informative message
  131. if (credentials == null)
  132. authenticationFailure("authenticate", "Credentials required");
  133. final String message =
  134. "Credentials should be String[] instead of " +
  135. credentials.getClass().getName();
  136. authenticationFailure("authenticate", message);
  137. }
  138. // Verify that the array contains two elements.
  139. //
  140. final String[] aCredentials = (String[]) credentials;
  141. if (aCredentials.length != 2) {
  142. final String message =
  143. "Credentials should have 2 elements not " +
  144. aCredentials.length;
  145. authenticationFailure("authenticate", message);
  146. }
  147. // Verify that username exists and the associated
  148. // password matches the one supplied by the client.
  149. //
  150. username = (String) aCredentials[0];
  151. password = (String) aCredentials[1];
  152. if (username == null || password == null) {
  153. final String message = "Username or password is null";
  154. authenticationFailure("authenticate", message);
  155. }
  156. // Perform authentication
  157. try {
  158. loginContext.login();
  159. final Subject subject = loginContext.getSubject();
  160. AccessController.doPrivileged(new PrivilegedAction() {
  161. public Object run() {
  162. subject.setReadOnly();
  163. return null;
  164. }
  165. });
  166. return subject;
  167. } catch (LoginException le) {
  168. authenticationFailure("authenticate", le);
  169. }
  170. return null;
  171. }
  172. private static void authenticationFailure(String method, String message)
  173. throws SecurityException {
  174. final String msg = "Authentication failed! " + message;
  175. final SecurityException e = new SecurityException(msg);
  176. logException(method, msg, e);
  177. throw e;
  178. }
  179. private static void authenticationFailure(String method,
  180. Exception exception)
  181. throws SecurityException {
  182. String msg;
  183. SecurityException se;
  184. if (exception instanceof SecurityException) {
  185. msg = exception.getMessage();
  186. se = (SecurityException) exception;
  187. } else {
  188. msg = "Authentication failed! " + exception.getMessage();
  189. final SecurityException e = new SecurityException(msg);
  190. EnvHelp.initCause(e, exception);
  191. se = e;
  192. }
  193. logException(method, msg, se);
  194. throw se;
  195. }
  196. private static void logException(String method,
  197. String message,
  198. Exception e) {
  199. if (logger.traceOn()) {
  200. logger.trace(method, message);
  201. }
  202. if (logger.debugOn()) {
  203. logger.debug(method, e);
  204. }
  205. }
  206. private LoginContext loginContext;
  207. private String username;
  208. private String password;
  209. private static final String LOGIN_CONFIG_PROP =
  210. "jmx.remote.x.login.config";
  211. private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator";
  212. private static final String PASSWORD_FILE_PROP =
  213. "jmx.remote.x.password.file";
  214. private static final ClassLogger logger =
  215. new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME);
  216. /**
  217. * This callback handler supplies the username and password (which was
  218. * originally supplied by the JMX user) to the JAAS login module performing
  219. * the authentication. No interactive user prompting is required because the
  220. * credentials are already available to this class (via its enclosing class).
  221. */
  222. private final class JMXCallbackHandler implements CallbackHandler {
  223. /**
  224. * Sets the username and password in the appropriate Callback object.
  225. */
  226. public void handle(Callback[] callbacks)
  227. throws IOException, UnsupportedCallbackException {
  228. for (int i = 0; i < callbacks.length; i++) {
  229. if (callbacks[i] instanceof NameCallback) {
  230. ((NameCallback)callbacks[i]).setName(username);
  231. } else if (callbacks[i] instanceof PasswordCallback) {
  232. ((PasswordCallback)callbacks[i])
  233. .setPassword(password.toCharArray());
  234. } else {
  235. throw new UnsupportedCallbackException
  236. (callbacks[i], "Unrecognized Callback");
  237. }
  238. }
  239. }
  240. }
  241. /**
  242. * This class defines the JAAS configuration for file-based authentication.
  243. * It is equivalent to the following textual configuration entry:
  244. * <pre>
  245. * JMXPluggableAuthenticator {
  246. * com.sun.jmx.remote.security.FileLoginModule required;
  247. * };
  248. * </pre>
  249. */
  250. private static class FileLoginConfig extends Configuration {
  251. // The JAAS configuration for file-based authentication
  252. private static AppConfigurationEntry[] entries;
  253. // The classname of the login module for file-based authentication
  254. private static final String FILE_LOGIN_MODULE =
  255. FileLoginModule.class.getName();
  256. // The option that identifies the password file to use
  257. private static final String PASSWORD_FILE_OPTION = "passwordFile";
  258. /**
  259. * Creates an instance of <code>FileLoginConfig</code>
  260. *
  261. * @param passwordFile A filepath that identifies the password file to use.
  262. * If null then the default password file is used.
  263. */
  264. public FileLoginConfig(String passwordFile) {
  265. Map options;
  266. if (passwordFile != null) {
  267. options = new HashMap(1);
  268. options.put(PASSWORD_FILE_OPTION, passwordFile);
  269. } else {
  270. options = Collections.EMPTY_MAP;
  271. }
  272. entries = new AppConfigurationEntry[] {
  273. new AppConfigurationEntry(FILE_LOGIN_MODULE,
  274. AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
  275. options)
  276. };
  277. }
  278. /**
  279. * Gets the JAAS configuration for file-based authentication
  280. */
  281. public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
  282. return name.equals(LOGIN_CONFIG_NAME) ? entries : null;
  283. }
  284. /**
  285. * Refreshes the configuration.
  286. */
  287. public void refresh() {
  288. // the configuration is fixed
  289. }
  290. }
  291. }