alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Tomcat example source code file (JAASRealm.java)

This example Tomcat source code file (JAASRealm.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Tomcat tags/keywords

arraylist, class, class, classloader, lifecycleexception, list, list, logincontext, principal, principal, security, string, string, subject, throwable, util

The Tomcat JAASRealm.java source code

/*
 * 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.juli.logging.Log;
import org.apache.juli.logging.LogFactory;


/**
 * <p>Implmentation of Realm that authenticates users via the 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 file).

* * <p>The value configured for the appName property is passed to * the <code>javax.security.auth.login.LoginContext constructor, to * specify the <em>application name
used to select the set of relevant * <code>LoginModules required.

* * <p>The JAAS Specification describes the result of a successful login as a * <code>javax.security.auth.Subject instance, which can contain zero * or more <code>java.security.Principal objects in the return value * of the <code>Subject.getPrincipals() 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 * implementation executed by JAAS, the following policy is implemented by * this Realm:</p> * <ul> * <li>The JAAS LoginModule is assumed to return a * <code>Subject with at least one Principal instance * representing the user himself or herself, and zero or more separate * <code>Principals representing the security roles authorized * for this user.</li> * <li>On the Principal representing the user, the Principal * name is an appropriate value to return via the Servlet API method * <code>HttpServletRequest.getRemoteUser(). * <li>On the Principals 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 - 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 Principals returned by * <code>Subject.getPrincipals(), it will identify the first * <code>Principal that matches the "user classes" list as the * <code>Principal for this user. * <li>As this Realm iterates over the Princpals returned by * <code>Subject.getPrincipals(), it will accumulate the set of * all <code>Principals 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 without a Principal 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>
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>
 * <li>To set the JAAS configuration file
 *     location, set the <code>CATALINA_OPTS environment variable
 *     similar to the following:
<blockquote>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"
 * </li>
 * <li>As part of the login process, JAASRealm registers its own CallbackHandler,
 *     called (unsurprisingly) <code>JAASCallbackHandler. This handler supplies the 
 *     HTTP requests's username and credentials to the user-supplied <code>LoginModule
 * <li>As with other Realm implementations, digested passwords are supported if
 *     the <code><Realm> element in server.xml contains a 
 *     <code>digest attribute; JAASCallbackHandler will digest the password
 *     prior to passing it back to the <code>LoginModule  
* </ul>
*
* @author Craig R. McClanahan
* @author Yoav Shapira
 * @version $Revision: 607339 $ $Date: 2007-12-28 22:31:46 +0100 (ven., 28 déc. 2007) $
 */

public class JAASRealm
    extends RealmBase
 {
    private static Log log = LogFactory.getLog(JAASRealm.class);

    // ----------------------------------------------------- Instance Variables


    /**
     * The application name passed to the JAAS <code>LoginContext,
     * which uses it to select the set of relevant <code>LoginModules.
     */
    protected String appName = null;


    /**
     * Descriptive information about this <code>Realm implementation.
     */
    protected static final String info =
        "org.apache.catalina.realm.JAASRealm/1.0";


    /**
     * Descriptive information about this <code>Realm implementation.
     */
    protected static final String name = "JAASRealm";


    /**
     * The list of role class names, split out for easy processing.
     */
    protected List<String> 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<String> 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 member variable
     * @deprecated JAAS should use the <code>Engine (domain) name and webpp/host overrides
     */
    public void setAppName(String name) {
        appName = name;
    }
    
    /**
     * getter for the <code>appName 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 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.
      * The supplied list of classes will be parsed when {@link #start()} is
      * called.
      */
     public void setRoleClassNames(String roleClassNames) {
         this.roleClassNames = roleClassNames;
     }
     
     /**
      * 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.
      * 
      * @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<String> classNamesList) {
         classNamesList.clear();
         if (classNamesString == null) return;

         ClassLoader loader = this.getClass().getClassLoader();
         if (isUseContextClassLoader())
             loader = Thread.currentThread().getContextClassLoader();

         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], false,
                         loader);
                 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 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. The supplied list of classes will
      * be parsed when {@link #start()} is called.
      */
    public void setUserClassNames(String userClassNames) {
        this.userClassNames = userClassNames;
    }


    // --------------------------------------------------------- Public Methods


    /**
     * Return the <code>Principal associated with the specified username and
     * credentials, if there is one; otherwise return <code>null.
     *
     * 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 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 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 associated with the given user name.
     */
    protected Principal getPrincipal(String username) {

        return (null);

    }


    /**
     * Identify and return a <code>java.security.Principal instance
     * representing the authenticated user for the specified <code>Subject.
     * The Principal is constructed by scanning the list of Principals returned
     * by the JAASLoginModule. The first <code>Principal 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.
     * @param subject The <code>Subject representing the logged-in user
     */
    protected Principal createPrincipal(String username, Subject subject) {
        // Prepare to scan the Principals for this Subject

        List<String> roles = new ArrayList();
        Principal userPrincipal = null;

        // Scan the Principals for this Subject
        Iterator<Principal> principals = subject.getPrincipals().iterator();
        while (principals.hasNext()) {
            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.
     *
     * @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();

        // These need to be called after loading configuration, in case
        // useContextClassLoader appears after them in xml config
        parseClassNames(userClassNames, userClasses);
        parseClassNames(roleClassNames, roleClasses);
    }


    /**
     * Gracefully shut down active use of the public methods of this <code>Component.
     *
     * @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();

    }


}

Other Tomcat examples (source code examples)

Here is a short list of links related to this Tomcat JAASRealm.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.