All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.realm.JAASRealm Maven / Gradle / Ivy

/*
 * Copyright 2001-2002,2004 The Apache Software Foundation.
 * 
 * Licensed 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.HashMap;
import java.util.Iterator;
import java.util.Map;
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;


/**
 * 

Implmentation of Realm that authenticates users via the Java * Authentication and Authorization Service (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 jaas.jar file).

* *

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

* *

The JAAS Specification describes the result of a successful login as a * javax.security.auth.Subject instance, which can contain zero * or more java.security.Principal objects in the return value * of the 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 LoginMethod * implementation executed by JAAS, the following policy is implemented by * this Realm:

*
    *
  • The JAAS LoginModule is assumed to return a * Subject with at least one Principal instance * representing the user himself or herself, and zero or more separate * Principals representing the security roles authorized * for this user.
  • *
  • On the Principal representing the user, the Principal * name is an appropriate value to return via the Servlet API method * HttpServletRequest.getRemoteUser().
  • *
  • On the Principals representing the security roles, the * name is the name of the authorized security role.
  • *
  • This Realm will be configured with two lists of fully qualified Java * class names of classes that implement * java.security.Principal - one that identifies class(es) * representing a user, and one that identifies class(es) representing * a security role.
  • *
  • As this Realm iterates over the Principals returned by * Subject.getPrincipals(), it will identify the first * Principal that matches the "user classes" list as the * Principal for this user.
  • *
  • As this Realm iterates over the Princpals returned by * Subject.getPrincipals(), it will accumulate the set of * all Principals matching the "role classes" list as * identifying the security roles for this user.
  • *
  • It is a configuration error for the JAAS login method to return a * validated Subject without a Principal that * matches the "user classes" list.
  • *
  • 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: *
    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;
    };
  • *
  • To set the JAAS configuration file * location, set the CATALINA_OPTS environment variable * similar to the following:
    CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"
    *
  • *
  • As part of the login process, JAASRealm registers its own CallbackHandler, * called (unsurprisingly) JAASCallbackHandler. This handler supplies the * HTTP requests's username and credentials to the user-supplied LoginModule
  • *
  • As with other Realm implementations, digested passwords are supported if * the <Realm> element in server.xml contains a * digest attribute; JAASCallbackHandler will digest the password * prior to passing it back to the LoginModule
  • *
* * @author Craig R. McClanahan * @author Yoav Shapira * @version $Revision: 1.11 $ $Date: 2004/10/06 16:11:34 $ */ public class JAASRealm extends RealmBase { private static Log log = LogFactory.getLog(JAASRealm.class); // ----------------------------------------------------- Instance Variables /** * The application name passed to the JAAS LoginContext, * which uses it to select the set of relevant LoginModules. */ protected String appName = null; /** * Descriptive information about this Realm implementation. */ protected static final String info = "org.apache.catalina.realm.JAASRealm/1.0"; /** * Descriptive information about this Realm 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(); /** * Map associating each user Principal object * with an array of role Principals. * This Map is read when hasRole is called. */ protected Map roleMap = new HashMap(); /** * 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 appName member variable * @deprecated JAAS should use the Engine (domain) name and webpp/host overrides */ public void setAppName(String name) { appName = name; } /** * getter for the 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 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 java.security.Principal. * When this accessor is called (for example, by a Digester * instance parsing the * configuration file), it will parse the class names and store the resulting * string(s) into the ArrayList field
roleClasses. */ public void setRoleClassNames(String roleClassNames) { this.roleClassNames = roleClassNames; roleClasses.clear(); String temp = this.roleClassNames; if (temp == null) { return; } while (true) { int comma = temp.indexOf(','); if (comma < 0) { break; } roleClasses.add(temp.substring(0, comma).trim()); temp = temp.substring(comma + 1); } temp = temp.trim(); if (temp.length() > 0) { roleClasses.add(temp); } } /** * Comma-delimited list of 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 java.security.Principal. * When this accessor is called (for example, by a Digester * instance parsing the * configuration file), it will parse the class names and store the resulting * string(s) into the ArrayList field userClasses. */ public void setUserClassNames(String userClassNames) { this.userClassNames = userClassNames; userClasses.clear(); String temp = this.userClassNames; if (temp == null) { return; } while (true) { int comma = temp.indexOf(','); if (comma < 0) { break; } userClasses.add(temp.substring(0, comma).trim()); temp = temp.substring(comma + 1); } temp = temp.trim(); if (temp.length() > 0) { userClasses.add(temp); } } // --------------------------------------------------------- Public Methods /** * Return the Principal associated with the specified username and * credentials, if there is one; otherwise return 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 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; } } /** * Returns true if the specified user Principal has the specified * security role, within the context of this Realm; otherwise return * false. This will be true when * an associated role Principal can be found whose getName * method returns a String equalling the specified role. * @param principal Principal for whom the role is to be checked * @param role Security role to be checked */ public boolean hasRole(Principal principal, String role) { if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.start", principal.getName(), role)); } if ((principal == null) || (role == null) || (roleMap.get(principal) == null)) { if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.noPrincipalOrRole")); } return false; } List roles = (List)roleMap.get(principal); if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.principalCached", String.valueOf(roles.size()))); } for (Iterator it = roles.iterator(); it.hasNext();) { Principal possessedRole = (Principal)it.next(); String possessedRoleName = possessedRole.getName(); if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.possessesRole", possessedRole.getName())); } if (possessedRoleName.equals(role)) { if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.match")); } return true; } } if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.isInRole.noMatch")); } return false; } // -------------------------------------------------------- Package Methods // ------------------------------------------------------ Protected Methods /** * Return a short name for this 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 Principal associated with the given user name. */ protected Principal getPrincipal(String username) { return (null); } /** * Identify and return a java.security.Principal instance * representing the authenticated user for the specified Subject. * The Principal is constructed by scanning the list of Principals returned * by the JAASLoginModule. The first 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 null. * @param subject The Subject representing the logged-in user */ protected Principal createPrincipal(String username, Subject subject) { // Prepare to scan the Principals for this Subject String password = null; // Will not be carried forward 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); 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")); } } else { roleMap.put(userPrincipal, roles); if (log.isDebugEnabled()) { log.debug(sm.getString("jaasRealm.rolePrincipalSuccess", String.valueOf(roles.size()))); log.debug(sm.getString("jaasRealm.cachePrincipal", userPrincipal.getName(), String.valueOf(roles.size()))); } } } // Return the resulting Principal for our authenticated user return 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 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(); } /** * Gracefully shut down active use of the public methods of this 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy