Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   
   18   
   19   package org.apache.catalina.realm;
   20   
   21   
   22   import java.security.Principal;
   23   import java.util.ArrayList;
   24   import java.util.Iterator;
   25   import java.util.List;
   26   
   27   import javax.security.auth.Subject;
   28   import javax.security.auth.login.AccountExpiredException;
   29   import javax.security.auth.login.CredentialExpiredException;
   30   import javax.security.auth.login.FailedLoginException;
   31   import javax.security.auth.login.LoginContext;
   32   import javax.security.auth.login.LoginException;
   33   
   34   import org.apache.catalina.Container;
   35   import org.apache.catalina.LifecycleException;
   36   import org.apache.catalina.util.StringManager;
   37   import org.apache.juli.logging.Log;
   38   import org.apache.juli.logging.LogFactory;
   39   
   40   
   41   /**
   42    * <p>Implmentation of <b>Realm</b> that authenticates users via the <em>Java
   43    * Authentication and Authorization Service</em> (JAAS).  JAAS support requires
   44    * either JDK 1.4 (which includes it as part of the standard platform) or
   45    * JDK 1.3 (with the plug-in <code>jaas.jar</code> file).</p>
   46    *
   47    * <p>The value configured for the <code>appName</code> property is passed to
   48    * the <code>javax.security.auth.login.LoginContext</code> constructor, to
   49    * specify the <em>application name</em> used to select the set of relevant
   50    * <code>LoginModules</code> required.</p>
   51    *
   52    * <p>The JAAS Specification describes the result of a successful login as a
   53    * <code>javax.security.auth.Subject</code> instance, which can contain zero
   54    * or more <code>java.security.Principal</code> objects in the return value
   55    * of the <code>Subject.getPrincipals()</code> method.  However, it provides
   56    * no guidance on how to distinguish Principals that describe the individual
   57    * user (and are thus appropriate to return as the value of
   58    * request.getUserPrincipal() in a web application) from the Principal(s)
   59    * that describe the authorized roles for this user.  To maintain as much
   60    * independence as possible from the underlying <code>LoginMethod</code>
   61    * implementation executed by JAAS, the following policy is implemented by
   62    * this Realm:</p>
   63    * <ul>
   64    * <li>The JAAS <code>LoginModule</code> is assumed to return a
   65    *     <code>Subject</code> with at least one <code>Principal</code> instance
   66    *     representing the user himself or herself, and zero or more separate
   67    *     <code>Principals</code> representing the security roles authorized
   68    *     for this user.</li>
   69    * <li>On the <code>Principal</code> representing the user, the Principal
   70    *     name is an appropriate value to return via the Servlet API method
   71    *     <code>HttpServletRequest.getRemoteUser()</code>.</li>
   72    * <li>On the <code>Principals</code> representing the security roles, the
   73    *     name is the name of the authorized security role.</li>
   74    * <li>This Realm will be configured with two lists of fully qualified Java
   75    *     class names of classes that implement
   76    *     <code>java.security.Principal</code> - one that identifies class(es)
   77    *     representing a user, and one that identifies class(es) representing
   78    *     a security role.</li>
   79    * <li>As this Realm iterates over the <code>Principals</code> returned by
   80    *     <code>Subject.getPrincipals()</code>, it will identify the first
   81    *     <code>Principal</code> that matches the "user classes" list as the
   82    *     <code>Principal</code> for this user.</li>
   83    * <li>As this Realm iterates over the <code>Princpals</code> returned by
   84    *     <code>Subject.getPrincipals()</code>, it will accumulate the set of
   85    *     all <code>Principals</code> matching the "role classes" list as
   86    *     identifying the security roles for this user.</li>
   87    * <li>It is a configuration error for the JAAS login method to return a
   88    *     validated <code>Subject</code> without a <code>Principal</code> that
   89    *     matches the "user classes" list.</li>
   90    * <li>By default, the enclosing Container's name serves as the
   91    *     application name used to obtain the JAAS LoginContext ("Catalina" in
   92    *     a default installation). Tomcat must be able to find an application
   93    *     with this name in the JAAS configuration file. Here is a hypothetical
   94    *     JAAS configuration file entry for a database-oriented login module that uses 
   95    *     a Tomcat-managed JNDI database resource:
   96    *     <blockquote><pre>Catalina {
   97   org.foobar.auth.DatabaseLoginModule REQUIRED
   98       JNDI_RESOURCE=jdbc/AuthDB
   99     USER_TABLE=users
  100     USER_ID_COLUMN=id
  101     USER_NAME_COLUMN=name
  102     USER_CREDENTIAL_COLUMN=password
  103     ROLE_TABLE=roles
  104     ROLE_NAME_COLUMN=name
  105     PRINCIPAL_FACTORY=org.foobar.auth.impl.SimplePrincipalFactory;
  106   };</pre></blockquote></li>
  107    * <li>To set the JAAS configuration file
  108    *     location, set the <code>CATALINA_OPTS</code> environment variable
  109    *     similar to the following:
  110   <blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote>
  111    * </li>
  112    * <li>As part of the login process, JAASRealm registers its own <code>CallbackHandler</code>,
  113    *     called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the 
  114    *     HTTP requests's username and credentials to the user-supplied <code>LoginModule</code></li>
  115    * <li>As with other <code>Realm</code> implementations, digested passwords are supported if
  116    *     the <code>&lt;Realm&gt;</code> element in <code>server.xml</code> contains a 
  117    *     <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest the password
  118    *     prior to passing it back to the <code>LoginModule</code></li>  
  119   * </ul>
  120   *
  121   * @author Craig R. McClanahan
  122   * @author Yoav Shapira
  123    * @version $Revision: 607339 $ $Date: 2007-12-28 22:31:46 +0100 (ven., 28 déc. 2007) $
  124    */
  125   
  126   public class JAASRealm
  127       extends RealmBase
  128    {
  129       private static Log log = LogFactory.getLog(JAASRealm.class);
  130   
  131       // ----------------------------------------------------- Instance Variables
  132   
  133   
  134       /**
  135        * The application name passed to the JAAS <code>LoginContext</code>,
  136        * which uses it to select the set of relevant <code>LoginModule</code>s.
  137        */
  138       protected String appName = null;
  139   
  140   
  141       /**
  142        * Descriptive information about this <code>Realm</code> implementation.
  143        */
  144       protected static final String info =
  145           "org.apache.catalina.realm.JAASRealm/1.0";
  146   
  147   
  148       /**
  149        * Descriptive information about this <code>Realm</code> implementation.
  150        */
  151       protected static final String name = "JAASRealm";
  152   
  153   
  154       /**
  155        * The list of role class names, split out for easy processing.
  156        */
  157       protected List<String> roleClasses = new ArrayList<String>();
  158   
  159   
  160       /**
  161        * The string manager for this package.
  162        */
  163       protected static final StringManager sm =
  164           StringManager.getManager(Constants.Package);
  165   
  166   
  167       /**
  168        * The set of user class names, split out for easy processing.
  169        */
  170       protected List<String> userClasses = new ArrayList<String>();
  171   
  172   
  173       /**
  174        * Whether to use context ClassLoader or default ClassLoader.
  175        * True means use context ClassLoader, and True is the default
  176        * value.
  177        */
  178        protected boolean useContextClassLoader = true;
  179   
  180   
  181       // ------------------------------------------------------------- Properties
  182   
  183       
  184       /**
  185        * setter for the <code>appName</code> member variable
  186        * @deprecated JAAS should use the <code>Engine</code> (domain) name and webpp/host overrides
  187        */
  188       public void setAppName(String name) {
  189           appName = name;
  190       }
  191       
  192       /**
  193        * getter for the <code>appName</code> member variable
  194        */
  195       public String getAppName() {
  196           return appName;
  197       }
  198   
  199       /**
  200        * Sets whether to use the context or default ClassLoader.
  201        * True means use context ClassLoader.
  202        *
  203        * @param useContext True means use context ClassLoader
  204        */
  205       public void setUseContextClassLoader(boolean useContext) {
  206         useContextClassLoader = useContext;
  207         log.info("Setting useContextClassLoader = " + useContext);
  208       }
  209   
  210       /**
  211        * Returns whether to use the context or default ClassLoader.
  212        * True means to use the context ClassLoader.
  213        *
  214        * @return The value of useContextClassLoader
  215        */
  216       public boolean isUseContextClassLoader() {
  217   	return useContextClassLoader;
  218       } 
  219   
  220       public void setContainer(Container container) {
  221           super.setContainer(container);
  222   
  223           if( appName==null  ) {
  224               String name=container.getName();
  225               name = makeLegalForJAAS(name);
  226   
  227               appName=name;
  228   
  229               log.info("Set JAAS app name " + appName);
  230           }
  231       }
  232   
  233        /**
  234         * Comma-delimited list of <code>java.security.Principal</code> classes
  235         * that represent security roles.
  236         */
  237        protected String roleClassNames = null;
  238        
  239        public String getRoleClassNames() {
  240            return (this.roleClassNames);
  241        }
  242        
  243        /**
  244         * Sets the list of comma-delimited classes that represent roles. The
  245         * classes in the list must implement <code>java.security.Principal</code>.
  246         * The supplied list of classes will be parsed when {@link #start()} is
  247         * called.
  248         */
  249        public void setRoleClassNames(String roleClassNames) {
  250            this.roleClassNames = roleClassNames;
  251        }
  252        
  253        /**
  254         * Parses a comma-delimited list of class names, and store the class names
  255         * in the provided List. Each class must implement
  256         * <code>java.security.Principal</code>.
  257         * 
  258         * @param classNamesString a comma-delimited list of fully qualified class names.
  259         * @param classNamesList the list in which the class names will be stored.
  260         *        The list is cleared before being populated. 
  261         */
  262        protected void parseClassNames(String classNamesString, List<String> classNamesList) {
  263            classNamesList.clear();
  264            if (classNamesString == null) return;
  265   
  266            ClassLoader loader = this.getClass().getClassLoader();
  267            if (isUseContextClassLoader())
  268                loader = Thread.currentThread().getContextClassLoader();
  269   
  270            String[] classNames = classNamesString.split("[ ]*,[ ]*");
  271            for (int i=0; i<classNames.length; i++) {
  272                if (classNames[i].length()==0) continue;        
  273                try {
  274                    Class principalClass = Class.forName(classNames[i], false,
  275                            loader);
  276                    if (Principal.class.isAssignableFrom(principalClass)) {
  277                        classNamesList.add(classNames[i]);
  278                    } else {
  279                        log.error("Class "+classNames[i]+" is not implementing "+
  280                                  "java.security.Principal! Class not added.");
  281                    }
  282                } catch (ClassNotFoundException e) {
  283                    log.error("Class "+classNames[i]+" not found! Class not added.");
  284                }
  285            }
  286        }     
  287        
  288        /**
  289         * Comma-delimited list of <code>java.security.Principal</code> classes
  290         * that represent individual users.
  291         */
  292        protected String userClassNames = null;
  293        
  294        public String getUserClassNames() {
  295            return (this.userClassNames);
  296        }
  297        
  298        /**
  299         * Sets the list of comma-delimited classes that represent individual
  300         * users. The classes in the list must implement
  301         * <code>java.security.Principal</code>. The supplied list of classes will
  302         * be parsed when {@link #start()} is called.
  303         */
  304       public void setUserClassNames(String userClassNames) {
  305           this.userClassNames = userClassNames;
  306       }
  307   
  308   
  309       // --------------------------------------------------------- Public Methods
  310   
  311   
  312       /**
  313        * Return the <code>Principal</code> associated with the specified username and
  314        * credentials, if there is one; otherwise return <code>null</code>.
  315        *
  316        * If there are any errors with the JDBC connection, executing
  317        * the query or anything we return null (don't authenticate). This
  318        * event is also logged, and the connection will be closed so that
  319        * a subsequent request will automatically re-open it.
  320        *
  321        * @param username Username of the <code>Principal</code> to look up
  322        * @param credentials Password or other credentials to use in
  323        *  authenticating this username
  324        */
  325       public Principal authenticate(String username, String credentials) {
  326   
  327           // Establish a LoginContext to use for authentication
  328           try {
  329           LoginContext loginContext = null;
  330           if( appName==null ) appName="Tomcat";
  331   
  332           if( log.isDebugEnabled())
  333               log.debug(sm.getString("jaasRealm.beginLogin", username, appName));
  334   
  335           // What if the LoginModule is in the container class loader ?
  336           ClassLoader ocl = null;
  337   
  338           if (!isUseContextClassLoader()) {
  339             ocl = Thread.currentThread().getContextClassLoader();
  340             Thread.currentThread().setContextClassLoader(
  341                     this.getClass().getClassLoader());
  342           }
  343   
  344           try {
  345               loginContext = new LoginContext
  346                   (appName, new JAASCallbackHandler(this, username,
  347                                                     credentials));
  348           } catch (Throwable e) {
  349               log.error(sm.getString("jaasRealm.unexpectedError"), e);
  350               return (null);
  351           } finally {
  352               if(!isUseContextClassLoader()) {
  353                 Thread.currentThread().setContextClassLoader(ocl);
  354               }
  355           }
  356   
  357           if( log.isDebugEnabled())
  358               log.debug("Login context created " + username);
  359   
  360           // Negotiate a login via this LoginContext
  361           Subject subject = null;
  362           try {
  363               loginContext.login();
  364               subject = loginContext.getSubject();
  365               if (subject == null) {
  366                   if( log.isDebugEnabled())
  367                       log.debug(sm.getString("jaasRealm.failedLogin", username));
  368                   return (null);
  369               }
  370           } catch (AccountExpiredException e) {
  371               if (log.isDebugEnabled())
  372                   log.debug(sm.getString("jaasRealm.accountExpired", username));
  373               return (null);
  374           } catch (CredentialExpiredException e) {
  375               if (log.isDebugEnabled())
  376                   log.debug(sm.getString("jaasRealm.credentialExpired", username));
  377               return (null);
  378           } catch (FailedLoginException e) {
  379               if (log.isDebugEnabled())
  380                   log.debug(sm.getString("jaasRealm.failedLogin", username));
  381               return (null);
  382           } catch (LoginException e) {
  383               log.warn(sm.getString("jaasRealm.loginException", username), e);
  384               return (null);
  385           } catch (Throwable e) {
  386               log.error(sm.getString("jaasRealm.unexpectedError"), e);
  387               return (null);
  388           }
  389   
  390           if( log.isDebugEnabled())
  391               log.debug(sm.getString("jaasRealm.loginContextCreated", username));
  392   
  393           // Return the appropriate Principal for this authenticated Subject
  394           Principal principal = createPrincipal(username, subject);
  395           if (principal == null) {
  396               log.debug(sm.getString("jaasRealm.authenticateFailure", username));
  397               return (null);
  398           }
  399           if (log.isDebugEnabled()) {
  400               log.debug(sm.getString("jaasRealm.authenticateSuccess", username));
  401           }
  402   
  403           return (principal);
  404           } catch( Throwable t) {
  405               log.error( "error ", t);
  406               return null;
  407           }
  408       }
  409        
  410   
  411       // -------------------------------------------------------- Package Methods
  412   
  413   
  414       // ------------------------------------------------------ Protected Methods
  415   
  416   
  417       /**
  418        * Return a short name for this <code>Realm</code> implementation.
  419        */
  420       protected String getName() {
  421   
  422           return (name);
  423   
  424       }
  425   
  426   
  427       /**
  428        * Return the password associated with the given principal's user name.
  429        */
  430       protected String getPassword(String username) {
  431   
  432           return (null);
  433   
  434       }
  435   
  436   
  437       /**
  438        * Return the <code>Principal</code> associated with the given user name.
  439        */
  440       protected Principal getPrincipal(String username) {
  441   
  442           return (null);
  443   
  444       }
  445   
  446   
  447       /**
  448        * Identify and return a <code>java.security.Principal</code> instance
  449        * representing the authenticated user for the specified <code>Subject</code>.
  450        * The Principal is constructed by scanning the list of Principals returned
  451        * by the JAASLoginModule. The first <code>Principal</code> object that matches
  452        * one of the class names supplied as a "user class" is the user Principal.
  453        * This object is returned to tha caller.
  454        * Any remaining principal objects returned by the LoginModules are mapped to  
  455        * roles, but only if their respective classes match one of the "role class" classes. 
  456        * If a user Principal cannot be constructed, return <code>null</code>.
  457        * @param subject The <code>Subject</code> representing the logged-in user
  458        */
  459       protected Principal createPrincipal(String username, Subject subject) {
  460           // Prepare to scan the Principals for this Subject
  461   
  462           List<String> roles = new ArrayList<String>();
  463           Principal userPrincipal = null;
  464   
  465           // Scan the Principals for this Subject
  466           Iterator<Principal> principals = subject.getPrincipals().iterator();
  467           while (principals.hasNext()) {
  468               Principal principal = principals.next();
  469   
  470               String principalClass = principal.getClass().getName();
  471   
  472               if( log.isDebugEnabled() ) {
  473                   log.debug(sm.getString("jaasRealm.checkPrincipal", principal, principalClass));
  474               }
  475   
  476               if (userPrincipal == null && userClasses.contains(principalClass)) {
  477                   userPrincipal = principal;
  478                   if( log.isDebugEnabled() ) {
  479                       log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName()));
  480                   }
  481               }
  482               
  483               if (roleClasses.contains(principalClass)) {
  484                   roles.add(principal.getName());
  485                   if( log.isDebugEnabled() ) {
  486                       log.debug(sm.getString("jaasRealm.rolePrincipalAdd", principal.getName()));
  487                   }
  488               }
  489           }
  490   
  491           // Print failure message if needed
  492           if (userPrincipal == null) {
  493               if (log.isDebugEnabled()) {
  494                   log.debug(sm.getString("jaasRealm.userPrincipalFailure"));
  495                   log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
  496               }
  497           } else {
  498               if (roles.size() == 0) {
  499                   if (log.isDebugEnabled()) {
  500                       log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
  501                   }
  502               }
  503           }
  504   
  505           // Return the resulting Principal for our authenticated user
  506           return new GenericPrincipal(this, username, null, roles, userPrincipal);
  507       }
  508   
  509        /**
  510         * Ensure the given name is legal for JAAS configuration.
  511         * Added for Bugzilla 30869, made protected for easy customization
  512         * in case my implementation is insufficient, which I think is
  513         * very likely.
  514         *
  515         * @param src The name to validate
  516         * @return A string that's a valid JAAS realm name
  517         */
  518        protected String makeLegalForJAAS(final String src) {
  519            String result = src;
  520            
  521            // Default name is "other" per JAAS spec
  522            if(result == null) {
  523                result = "other";
  524            }
  525   
  526            // Strip leading slash if present, as Sun JAAS impl
  527            // barfs on it (see Bugzilla 30869 bug report).
  528            if(result.startsWith("/")) {
  529                result = result.substring(1);
  530            }
  531   
  532            return result;
  533        }
  534   
  535   
  536       // ------------------------------------------------------ Lifecycle Methods
  537   
  538   
  539       /**
  540        *
  541        * Prepare for active use of the public methods of this <code>Component</code>.
  542        *
  543        * @exception LifecycleException if this component detects a fatal error
  544        *  that prevents it from being started
  545        */
  546       public void start() throws LifecycleException {
  547   
  548           // Perform normal superclass initialization
  549           super.start();
  550   
  551           // These need to be called after loading configuration, in case
  552           // useContextClassLoader appears after them in xml config
  553           parseClassNames(userClassNames, userClasses);
  554           parseClassNames(roleClassNames, roleClasses);
  555       }
  556   
  557   
  558       /**
  559        * Gracefully shut down active use of the public methods of this <code>Component</code>.
  560        *
  561        * @exception LifecycleException if this component detects a fatal error
  562        *  that needs to be reported
  563        */
  564       public void stop() throws LifecycleException {
  565   
  566           // Perform normal superclass finalization
  567           super.stop();
  568   
  569       }
  570   
  571   
  572   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]