| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.catalina.realm; |
| |
| |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.login.AccountExpiredException; |
| import javax.security.auth.login.CredentialExpiredException; |
| import javax.security.auth.login.FailedLoginException; |
| import javax.security.auth.login.LoginContext; |
| import javax.security.auth.login.LoginException; |
| |
| import org.apache.catalina.Container; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.util.StringManager; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| |
| /** |
| * <p>Implmentation of <b>Realm</b> that authenticates users via the <em>Java |
| * Authentication and Authorization Service</em> (JAAS). JAAS support requires |
| * either JDK 1.4 (which includes it as part of the standard platform) or |
| * JDK 1.3 (with the plug-in <code>jaas.jar</code> file).</p> |
| * |
| * <p>The value configured for the <code>appName</code> property is passed to |
| * the <code>javax.security.auth.login.LoginContext</code> constructor, to |
| * specify the <em>application name</em> used to select the set of relevant |
| * <code>LoginModules</code> required.</p> |
| * |
| * <p>The JAAS Specification describes the result of a successful login as a |
| * <code>javax.security.auth.Subject</code> instance, which can contain zero |
| * or more <code>java.security.Principal</code> objects in the return value |
| * of the <code>Subject.getPrincipals()</code> method. However, it provides |
| * no guidance on how to distinguish Principals that describe the individual |
| * user (and are thus appropriate to return as the value of |
| * request.getUserPrincipal() in a web application) from the Principal(s) |
| * that describe the authorized roles for this user. To maintain as much |
| * independence as possible from the underlying <code>LoginMethod</code> |
| * implementation executed by JAAS, the following policy is implemented by |
| * this Realm:</p> |
| * <ul> |
| * <li>The JAAS <code>LoginModule</code> is assumed to return a |
| * <code>Subject</code> with at least one <code>Principal</code> instance |
| * representing the user himself or herself, and zero or more separate |
| * <code>Principals</code> representing the security roles authorized |
| * for this user.</li> |
| * <li>On the <code>Principal</code> representing the user, the Principal |
| * name is an appropriate value to return via the Servlet API method |
| * <code>HttpServletRequest.getRemoteUser()</code>.</li> |
| * <li>On the <code>Principals</code> representing the security roles, the |
| * name is the name of the authorized security role.</li> |
| * <li>This Realm will be configured with two lists of fully qualified Java |
| * class names of classes that implement |
| * <code>java.security.Principal</code> - one that identifies class(es) |
| * representing a user, and one that identifies class(es) representing |
| * a security role.</li> |
| * <li>As this Realm iterates over the <code>Principals</code> returned by |
| * <code>Subject.getPrincipals()</code>, it will identify the first |
| * <code>Principal</code> that matches the "user classes" list as the |
| * <code>Principal</code> for this user.</li> |
| * <li>As this Realm iterates over the <code>Princpals</code> returned by |
| * <code>Subject.getPrincipals()</code>, it will accumulate the set of |
| * all <code>Principals</code> matching the "role classes" list as |
| * identifying the security roles for this user.</li> |
| * <li>It is a configuration error for the JAAS login method to return a |
| * validated <code>Subject</code> without a <code>Principal</code> that |
| * matches the "user classes" list.</li> |
| * <li>By default, the enclosing Container's name serves as the |
| * application name used to obtain the JAAS LoginContext ("Catalina" in |
| * a default installation). Tomcat must be able to find an application |
| * with this name in the JAAS configuration file. Here is a hypothetical |
| * JAAS configuration file entry for a database-oriented login module that uses |
| * a Tomcat-managed JNDI database resource: |
| * <blockquote><pre>Catalina { |
| org.foobar.auth.DatabaseLoginModule REQUIRED |
| JNDI_RESOURCE=jdbc/AuthDB |
| USER_TABLE=users |
| USER_ID_COLUMN=id |
| USER_NAME_COLUMN=name |
| USER_CREDENTIAL_COLUMN=password |
| ROLE_TABLE=roles |
| ROLE_NAME_COLUMN=name |
| PRINCIPAL_FACTORY=org.foobar.auth.impl.SimplePrincipalFactory; |
| };</pre></blockquote></li> |
| * <li>To set the JAAS configuration file |
| * location, set the <code>CATALINA_OPTS</code> environment variable |
| * similar to the following: |
| <blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote> |
| * </li> |
| * <li>As part of the login process, JAASRealm registers its own <code>CallbackHandler</code>, |
| * called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the |
| * HTTP requests's username and credentials to the user-supplied <code>LoginModule</code></li> |
| * <li>As with other <code>Realm</code> implementations, digested passwords are supported if |
| * the <code><Realm></code> element in <code>server.xml</code> contains a |
| * <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest the password |
| * prior to passing it back to the <code>LoginModule</code></li> |
| * </ul> |
| * |
| * @author Craig R. McClanahan |
| * @author Yoav Shapira |
| * @version $Revision$ $Date$ |
| */ |
| |
| public class JAASRealm |
| extends RealmBase |
| { |
| private static Log log = LogFactory.getLog(JAASRealm.class); |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * The application name passed to the JAAS <code>LoginContext</code>, |
| * which uses it to select the set of relevant <code>LoginModule</code>s. |
| */ |
| protected String appName = null; |
| |
| |
| /** |
| * Descriptive information about this <code>Realm</code> implementation. |
| */ |
| protected static final String info = |
| "org.apache.catalina.realm.JAASRealm/1.0"; |
| |
| |
| /** |
| * Descriptive information about this <code>Realm</code> implementation. |
| */ |
| protected static final String name = "JAASRealm"; |
| |
| |
| /** |
| * The list of role class names, split out for easy processing. |
| */ |
| protected List roleClasses = new ArrayList(); |
| |
| |
| /** |
| * The string manager for this package. |
| */ |
| protected static final StringManager sm = |
| StringManager.getManager(Constants.Package); |
| |
| |
| /** |
| * The set of user class names, split out for easy processing. |
| */ |
| protected List userClasses = new ArrayList(); |
| |
| |
| /** |
| * Whether to use context ClassLoader or default ClassLoader. |
| * True means use context ClassLoader, and True is the default |
| * value. |
| */ |
| protected boolean useContextClassLoader = true; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| /** |
| * setter for the <code>appName</code> member variable |
| * @deprecated JAAS should use the <code>Engine</code> (domain) name and webpp/host overrides |
| */ |
| public void setAppName(String name) { |
| appName = name; |
| } |
| |
| /** |
| * getter for the <code>appName</code> member variable |
| */ |
| public String getAppName() { |
| return appName; |
| } |
| |
| /** |
| * Sets whether to use the context or default ClassLoader. |
| * True means use context ClassLoader. |
| * |
| * @param useContext True means use context ClassLoader |
| */ |
| public void setUseContextClassLoader(boolean useContext) { |
| useContextClassLoader = useContext; |
| log.info("Setting useContextClassLoader = " + useContext); |
| } |
| |
| /** |
| * Returns whether to use the context or default ClassLoader. |
| * True means to use the context ClassLoader. |
| * |
| * @return The value of useContextClassLoader |
| */ |
| public boolean isUseContextClassLoader() { |
| return useContextClassLoader; |
| } |
| |
| public void setContainer(Container container) { |
| super.setContainer(container); |
| |
| if( appName==null ) { |
| String name=container.getName(); |
| name = makeLegalForJAAS(name); |
| |
| appName=name; |
| |
| log.info("Set JAAS app name " + appName); |
| } |
| } |
| |
| /** |
| * Comma-delimited list of <code>java.security.Principal</code> classes |
| * that represent security roles. |
| */ |
| protected String roleClassNames = null; |
| |
| public String getRoleClassNames() { |
| return (this.roleClassNames); |
| } |
| |
| /** |
| * Sets the list of comma-delimited classes that represent |
| * roles. The classes in the list must implement <code>java.security.Principal</code>. |
| * When this accessor is called (for example, by a <code>Digester</code> |
| * instance parsing the |
| * configuration file), it will parse the class names and store the resulting |
| * string(s) into the <code>ArrayList</code> field </code>roleClasses</code>. |
| */ |
| public void setRoleClassNames(String roleClassNames) { |
| this.roleClassNames = roleClassNames; |
| parseClassNames(roleClassNames, roleClasses); |
| } |
| |
| /** |
| * Parses a comma-delimited list of class names, and store the class names |
| * in the provided List. Each class must implement <code>java.security.Principal</code>. |
| * |
| * @param classNamesString a comma-delimited list of fully qualified class names. |
| * @param classNamesList the list in which the class names will be stored. |
| * The list is cleared before being populated. |
| */ |
| protected void parseClassNames(String classNamesString, List classNamesList) { |
| classNamesList.clear(); |
| if (classNamesString == null) return; |
| |
| String[] classNames = classNamesString.split("[ ]*,[ ]*"); |
| for (int i=0; i<classNames.length; i++) { |
| if (classNames[i].length()==0) continue; |
| try { |
| Class principalClass = Class.forName(classNames[i]); |
| if (Principal.class.isAssignableFrom(principalClass)) { |
| classNamesList.add(classNames[i]); |
| } else { |
| log.error("Class "+classNames[i]+" is not implementing "+ |
| "java.security.Principal! Class not added."); |
| } |
| } catch (ClassNotFoundException e) { |
| log.error("Class "+classNames[i]+" not found! Class not added."); |
| } |
| } |
| } |
| |
| /** |
| * Comma-delimited list of <code>java.security.Principal</code> classes |
| * that represent individual users. |
| */ |
| protected String userClassNames = null; |
| |
| public String getUserClassNames() { |
| return (this.userClassNames); |
| } |
| |
| /** |
| * Sets the list of comma-delimited classes that represent individual |
| * users. The classes in the list must implement <code>java.security.Principal</code>. |
| * When this accessor is called (for example, by a <code>Digester</code> |
| * instance parsing the |
| * configuration file), it will parse the class names and store the resulting |
| * string(s) into the <code>ArrayList</code> field </code>userClasses</code>. |
| */ |
| public void setUserClassNames(String userClassNames) { |
| this.userClassNames = userClassNames; |
| parseClassNames(userClassNames, userClasses); |
| } |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| /** |
| * Return the <code>Principal</code> associated with the specified username and |
| * credentials, if there is one; otherwise return <code>null</code>. |
| * |
| * If there are any errors with the JDBC connection, executing |
| * the query or anything we return null (don't authenticate). This |
| * event is also logged, and the connection will be closed so that |
| * a subsequent request will automatically re-open it. |
| * |
| * @param username Username of the <code>Principal</code> to look up |
| * @param credentials Password or other credentials to use in |
| * authenticating this username |
| */ |
| public Principal authenticate(String username, String credentials) { |
| |
| // Establish a LoginContext to use for authentication |
| try { |
| LoginContext loginContext = null; |
| if( appName==null ) appName="Tomcat"; |
| |
| if( log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.beginLogin", username, appName)); |
| |
| // What if the LoginModule is in the container class loader ? |
| ClassLoader ocl = null; |
| |
| if (isUseContextClassLoader()) { |
| ocl=Thread.currentThread().getContextClassLoader(); |
| Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); |
| } |
| |
| try { |
| loginContext = new LoginContext |
| (appName, new JAASCallbackHandler(this, username, |
| credentials)); |
| } catch (Throwable e) { |
| log.error(sm.getString("jaasRealm.unexpectedError"), e); |
| return (null); |
| } finally { |
| if( isUseContextClassLoader()) { |
| Thread.currentThread().setContextClassLoader(ocl); |
| } |
| } |
| |
| if( log.isDebugEnabled()) |
| log.debug("Login context created " + username); |
| |
| // Negotiate a login via this LoginContext |
| Subject subject = null; |
| try { |
| loginContext.login(); |
| subject = loginContext.getSubject(); |
| if (subject == null) { |
| if( log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.failedLogin", username)); |
| return (null); |
| } |
| } catch (AccountExpiredException e) { |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.accountExpired", username)); |
| return (null); |
| } catch (CredentialExpiredException e) { |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.credentialExpired", username)); |
| return (null); |
| } catch (FailedLoginException e) { |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.failedLogin", username)); |
| return (null); |
| } catch (LoginException e) { |
| log.warn(sm.getString("jaasRealm.loginException", username), e); |
| return (null); |
| } catch (Throwable e) { |
| log.error(sm.getString("jaasRealm.unexpectedError"), e); |
| return (null); |
| } |
| |
| if( log.isDebugEnabled()) |
| log.debug(sm.getString("jaasRealm.loginContextCreated", username)); |
| |
| // Return the appropriate Principal for this authenticated Subject |
| Principal principal = createPrincipal(username, subject); |
| if (principal == null) { |
| log.debug(sm.getString("jaasRealm.authenticateFailure", username)); |
| return (null); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("jaasRealm.authenticateSuccess", username)); |
| } |
| |
| return (principal); |
| } catch( Throwable t) { |
| log.error( "error ", t); |
| return null; |
| } |
| } |
| |
| |
| // -------------------------------------------------------- Package Methods |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Return a short name for this <code>Realm</code> implementation. |
| */ |
| protected String getName() { |
| |
| return (name); |
| |
| } |
| |
| |
| /** |
| * Return the password associated with the given principal's user name. |
| */ |
| protected String getPassword(String username) { |
| |
| return (null); |
| |
| } |
| |
| |
| /** |
| * Return the <code>Principal</code> associated with the given user name. |
| */ |
| protected Principal getPrincipal(String username) { |
| |
| return (null); |
| |
| } |
| |
| |
| /** |
| * Identify and return a <code>java.security.Principal</code> instance |
| * representing the authenticated user for the specified <code>Subject</code>. |
| * The Principal is constructed by scanning the list of Principals returned |
| * by the JAASLoginModule. The first <code>Principal</code> object that matches |
| * one of the class names supplied as a "user class" is the user Principal. |
| * This object is returned to tha caller. |
| * Any remaining principal objects returned by the LoginModules are mapped to |
| * roles, but only if their respective classes match one of the "role class" classes. |
| * If a user Principal cannot be constructed, return <code>null</code>. |
| * @param subject The <code>Subject</code> representing the logged-in user |
| */ |
| protected Principal createPrincipal(String username, Subject subject) { |
| // Prepare to scan the Principals for this Subject |
| |
| List roles = new ArrayList(); |
| Principal userPrincipal = null; |
| |
| // Scan the Principals for this Subject |
| Iterator principals = subject.getPrincipals().iterator(); |
| while (principals.hasNext()) { |
| Principal principal = (Principal) principals.next(); |
| |
| String principalClass = principal.getClass().getName(); |
| |
| if( log.isDebugEnabled() ) { |
| log.debug(sm.getString("jaasRealm.checkPrincipal", principal, principalClass)); |
| } |
| |
| if (userPrincipal == null && userClasses.contains(principalClass)) { |
| userPrincipal = principal; |
| if( log.isDebugEnabled() ) { |
| log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName())); |
| } |
| } |
| |
| if (roleClasses.contains(principalClass)) { |
| roles.add(principal.getName()); |
| if( log.isDebugEnabled() ) { |
| log.debug(sm.getString("jaasRealm.rolePrincipalAdd", principal.getName())); |
| } |
| } |
| } |
| |
| // Print failure message if needed |
| if (userPrincipal == null) { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("jaasRealm.userPrincipalFailure")); |
| log.debug(sm.getString("jaasRealm.rolePrincipalFailure")); |
| } |
| } else { |
| if (roles.size() == 0) { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("jaasRealm.rolePrincipalFailure")); |
| } |
| } |
| } |
| |
| // Return the resulting Principal for our authenticated user |
| return new GenericPrincipal(this, username, null, roles, userPrincipal); |
| } |
| |
| /** |
| * Ensure the given name is legal for JAAS configuration. |
| * Added for Bugzilla 30869, made protected for easy customization |
| * in case my implementation is insufficient, which I think is |
| * very likely. |
| * |
| * @param src The name to validate |
| * @return A string that's a valid JAAS realm name |
| */ |
| protected String makeLegalForJAAS(final String src) { |
| String result = src; |
| |
| // Default name is "other" per JAAS spec |
| if(result == null) { |
| result = "other"; |
| } |
| |
| // Strip leading slash if present, as Sun JAAS impl |
| // barfs on it (see Bugzilla 30869 bug report). |
| if(result.startsWith("/")) { |
| result = result.substring(1); |
| } |
| |
| return result; |
| } |
| |
| |
| // ------------------------------------------------------ Lifecycle Methods |
| |
| |
| /** |
| * |
| * Prepare for active use of the public methods of this <code>Component</code>. |
| * |
| * @exception LifecycleException if this component detects a fatal error |
| * that prevents it from being started |
| */ |
| public void start() throws LifecycleException { |
| |
| // Perform normal superclass initialization |
| super.start(); |
| |
| } |
| |
| |
| /** |
| * Gracefully shut down active use of the public methods of this <code>Component</code>. |
| * |
| * @exception LifecycleException if this component detects a fatal error |
| * that needs to be reported |
| */ |
| public void stop() throws LifecycleException { |
| |
| // Perform normal superclass finalization |
| super.stop(); |
| |
| } |
| |
| |
| } |