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; /** *
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 ofImplmentation 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 * thejavax.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 morejava.security.Principal
objects in the return value * of theSubject.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 underlyingLoginMethod
* implementation executed by JAAS, the following policy is implemented by * this Realm:*
* * @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- The JAAS
*LoginModule
is assumed to return a *Subject
with at least onePrincipal
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 * allPrincipals
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 aPrincipal
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-suppliedLoginModule
- As with other
*Realm
implementations, digested passwords are supported if * the<Realm>
element inserver.xml
contains a *digest
attribute;JAASCallbackHandler
will digest the password * prior to passing it back to theLoginModule
LoginContext
, * which uses it to select the set of relevantLoginModule
s. */ protected String appName = null; /** * Descriptive information about thisRealm
implementation. */ protected static final String info = "org.apache.catalina.realm.JAASRealm/1.0"; /** * Descriptive information about thisRealm
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 userPrincipal
object * with an array of rolePrincipal
s. * This Map is read whenhasRole
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 theappName
member variable * @deprecated JAAS should use theEngine
(domain) name and webpp/host overrides */ public void setAppName(String name) { appName = name; } /** * getter for theappName
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 ofjava.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 implementjava.security.Principal
. * When this accessor is called (for example, by aDigester
* instance parsing the * configuration file), it will parse the class names and store the resulting * string(s) into theArrayList
fieldjava.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 implementjava.security.Principal
. * When this accessor is called (for example, by aDigester
* instance parsing the * configuration file), it will parse the class names and store the resulting * string(s) into theArrayList
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 thePrincipal
associated with the specified username and * credentials, if there is one; otherwise returnnull
. * * 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 thePrincipal
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; } } /** * Returnstrue
if the specified userPrincipal
has the specified * security role, within the context of thisRealm
; otherwise return *false
. This will be true when * an associated rolePrincipal
can be found whosegetName
* method returns aString
equalling the specified role. * @param principalPrincipal
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 thisRealm
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 thePrincipal
associated with the given user name. */ protected Principal getPrincipal(String username) { return (null); } /** * Identify and return ajava.security.Principal
instance * representing the authenticated user for the specifiedSubject
. * The Principal is constructed by scanning the list of Principals returned * by the JAASLoginModule. The firstPrincipal
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, returnnull
. * @param subject TheSubject
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 thisComponent
. * * @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 thisComponent
. * * @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(); } }