001: /*
002: * Copyright 2001-2002,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.realm;
018:
019: import java.security.Principal;
020: import java.security.acl.Group;
021: import java.util.ArrayList;
022: import java.util.Enumeration;
023: import java.util.Iterator;
024:
025: import javax.security.auth.Subject;
026: import javax.security.auth.login.AccountExpiredException;
027: import javax.security.auth.login.CredentialExpiredException;
028: import javax.security.auth.login.FailedLoginException;
029: import javax.security.auth.login.LoginContext;
030: import javax.security.auth.login.LoginException;
031:
032: import org.apache.catalina.Container;
033: import org.apache.catalina.LifecycleException;
034: import org.apache.catalina.util.StringManager;
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037:
038: /**
039: * <p>Implmentation of <b>Realm</b> that authenticates users via the <em>Java
040: * Authentication and Authorization Service</em> (JAAS). JAAS support requires
041: * either JDK 1.4 (which includes it as part of the standard platform) or
042: * JDK 1.3 (with the plug-in <code>jaas.jar</code> file).</p>
043: *
044: * <p>The value configured for the <code>appName</code> property is passed to
045: * the <code>javax.security.auth.login.LoginContext</code> constructor, to
046: * specify the <em>application name</em> used to select the set of relevant
047: * <code>LoginModules</code> required.</p>
048: *
049: * <p>The JAAS Specification describes the result of a successful login as a
050: * <code>javax.security.auth.Subject</code> instance, which can contain zero
051: * or more <code>java.security.Principal</code> objects in the return value
052: * of the <code>Subject.getPrincipals()</code> method. However, it provides
053: * no guidance on how to distinguish Principals that describe the individual
054: * user (and are thus appropriate to return as the value of
055: * request.getUserPrincipal() in a web application) from the Principal(s)
056: * that describe the authorized roles for this user. To maintain as much
057: * independence as possible from the underlying <code>LoginMethod</code>
058: * implementation executed by JAAS, the following policy is implemented by
059: * this Realm:</p>
060: * <ul>
061: * <li>The JAAS <code>LoginModule</code> is assumed to return a
062: * <code>Subject with at least one <code>Principal</code> instance
063: * representing the user himself or herself, and zero or more separate
064: * <code>Principals</code> representing the security roles authorized
065: * for this user.</li>
066: * <li>On the <code>Principal</code> representing the user, the Principal
067: * name is an appropriate value to return via the Servlet API method
068: * <code>HttpServletRequest.getRemoteUser()</code>.</li>
069: * <li>On the <code>Principals</code> representing the security roles, the
070: * name is the name of the authorized security role.</li>
071: * <li>This Realm will be configured with two lists of fully qualified Java
072: * class names of classes that implement
073: * <code>java.security.Principal</code> - one that identifies class(es)
074: * representing a user, and one that identifies class(es) representing
075: * a security role.</li>
076: * <li>As this Realm iterates over the <code>Principals</code> returned by
077: * <code>Subject.getPrincipals()</code>, it will identify the first
078: * <code>Principal</code> that matches the "user classes" list as the
079: * <code>Principal</code> for this user.</li>
080: * <li>As this Realm iterates over the <code>Princpals</code> returned by
081: * <code>Subject.getPrincipals()</code>, it will accumulate the set of
082: * all <code>Principals</code> matching the "role classes" list as
083: * identifying the security roles for this user.</li>
084: * <li>It is a configuration error for the JAAS login method to return a
085: * validated <code>Subject</code> without a <code>Principal</code> that
086: * matches the "user classes" list.</li>
087: * </ul>
088: *
089: * @author Craig R. McClanahan
090: * @author Yoav Shapira
091: * @version $Revision: 1.6.2.1 $ $Date: 2004/08/21 15:49:53 $
092: */
093:
094: public class JAASRealm extends RealmBase {
095: private static Log log = LogFactory.getLog(JAASRealm.class);
096:
097: // ----------------------------------------------------- Instance Variables
098:
099: /**
100: * The application name passed to the JAAS <code>LoginContext</code>,
101: * which uses it to select the set of relevant <code>LoginModules</code>.
102: */
103: protected String appName = null;
104:
105: /**
106: * Descriptive information about this Realm implementation.
107: */
108: protected static final String info = "org.apache.catalina.realm.JAASRealm/1.0";
109:
110: /**
111: * Descriptive information about this Realm implementation.
112: */
113: protected static final String name = "JAASRealm";
114:
115: /**
116: * The list of role class names, split out for easy processing.
117: */
118: protected ArrayList roleClasses = new ArrayList();
119:
120: /**
121: * The string manager for this package.
122: */
123: protected static final StringManager sm = StringManager
124: .getManager(Constants.Package);
125:
126: /**
127: * The set of user class names, split out for easy processing.
128: */
129: protected ArrayList userClasses = new ArrayList();
130:
131: /**
132: * Whether to use context ClassLoader or default ClassLoader.
133: * True means use context ClassLoader, and True is the default
134: * value.
135: */
136: protected boolean useContextClassLoader = true;
137:
138: // ------------------------------------------------------------- Properties
139:
140: /**
141: * setter for the appName member variable
142: * @deprecated JAAS should use the Engine ( domain ) name and webpp/host overrides
143: */
144: public void setAppName(String name) {
145: appName = name;
146: }
147:
148: /**
149: * getter for the appName member variable
150: */
151: public String getAppName() {
152: return appName;
153: }
154:
155: /**
156: * Sets whether to use the context or default ClassLoader.
157: * True means use context ClassLoader.
158: *
159: * @param useContext True means use context ClassLoader
160: */
161: public void setUseContextClassLoader(boolean useContext) {
162: useContextClassLoader = useContext;
163: log.info("Setting useContextClassLoader = " + useContext);
164: }
165:
166: /**
167: * Returns whether to use the context or default ClassLoader.
168: * True means to use the context ClassLoader.
169: *
170: * @return The value of useContextClassLoader
171: */
172: public boolean isUseContextClassLoader() {
173: return useContextClassLoader;
174: }
175:
176: public void setContainer(Container container) {
177: super .setContainer(container);
178: String name = container.getName();
179: if (appName == null) {
180: appName = name;
181: log.info("Setting JAAS app name " + appName);
182: }
183: }
184:
185: /**
186: * Comma-delimited list of <code>javax.security.Principal</code> classes
187: * that represent security roles.
188: */
189: protected String roleClassNames = null;
190:
191: public String getRoleClassNames() {
192: return (this .roleClassNames);
193: }
194:
195: public void setRoleClassNames(String roleClassNames) {
196: this .roleClassNames = roleClassNames;
197: roleClasses.clear();
198: String temp = this .roleClassNames;
199: if (temp == null) {
200: return;
201: }
202: while (true) {
203: int comma = temp.indexOf(',');
204: if (comma < 0) {
205: break;
206: }
207: roleClasses.add(temp.substring(0, comma).trim());
208: temp = temp.substring(comma + 1);
209: }
210: temp = temp.trim();
211: if (temp.length() > 0) {
212: roleClasses.add(temp);
213: }
214: }
215:
216: /**
217: * Comma-delimited list of <code>javax.security.Principal</code> classes
218: * that represent individual users.
219: */
220: protected String userClassNames = null;
221:
222: public String getUserClassNames() {
223: return (this .userClassNames);
224: }
225:
226: public void setUserClassNames(String userClassNames) {
227: this .userClassNames = userClassNames;
228: userClasses.clear();
229: String temp = this .userClassNames;
230: if (temp == null) {
231: return;
232: }
233: while (true) {
234: int comma = temp.indexOf(',');
235: if (comma < 0) {
236: break;
237: }
238: userClasses.add(temp.substring(0, comma).trim());
239: temp = temp.substring(comma + 1);
240: }
241: temp = temp.trim();
242: if (temp.length() > 0) {
243: userClasses.add(temp);
244: }
245: }
246:
247: // --------------------------------------------------------- Public Methods
248:
249: /**
250: * Return the Principal associated with the specified username and
251: * credentials, if there is one; otherwise return <code>null</code>.
252: *
253: * If there are any errors with the JDBC connection, executing
254: * the query or anything we return null (don't authenticate). This
255: * event is also logged, and the connection will be closed so that
256: * a subsequent request will automatically re-open it.
257: *
258: * @param username Username of the Principal to look up
259: * @param credentials Password or other credentials to use in
260: * authenticating this username
261: */
262: public Principal authenticate(String username, String credentials) {
263:
264: // Establish a LoginContext to use for authentication
265: try {
266: LoginContext loginContext = null;
267: if (appName == null)
268: appName = "Tomcat";
269:
270: if (log.isDebugEnabled())
271: log.debug("Authenticating " + appName + " " + username);
272:
273: // What if the LoginModule is in the container class loader ?
274: ClassLoader ocl = null;
275:
276: if (isUseContextClassLoader()) {
277: ocl = Thread.currentThread().getContextClassLoader();
278: Thread.currentThread().setContextClassLoader(
279: this .getClass().getClassLoader());
280: }
281:
282: try {
283: loginContext = new LoginContext(appName,
284: new JAASCallbackHandler(this , username,
285: credentials));
286: } catch (Throwable e) {
287: log.error(sm.getString("jaasRealm.unexpectedError"), e);
288: return (null);
289: } finally {
290: if (isUseContextClassLoader()) {
291: Thread.currentThread().setContextClassLoader(ocl);
292: }
293: }
294:
295: if (log.isDebugEnabled())
296: log.debug("Login context created " + username);
297:
298: // Negotiate a login via this LoginContext
299: Subject subject = null;
300: try {
301: loginContext.login();
302: subject = loginContext.getSubject();
303: if (subject == null) {
304: if (log.isDebugEnabled())
305: log.debug(sm.getString("jaasRealm.failedLogin",
306: username));
307: return (null);
308: }
309: } catch (AccountExpiredException e) {
310: if (log.isDebugEnabled())
311: log.debug(sm.getString("jaasRealm.accountExpired",
312: username));
313: return (null);
314: } catch (CredentialExpiredException e) {
315: if (log.isDebugEnabled())
316: log.debug(sm.getString(
317: "jaasRealm.credentialExpired", username));
318: return (null);
319: } catch (FailedLoginException e) {
320: if (log.isDebugEnabled())
321: log.debug(sm.getString("jaasRealm.failedLogin",
322: username));
323: return (null);
324: } catch (LoginException e) {
325: log.warn(sm.getString("jaasRealm.loginException",
326: username), e);
327: return (null);
328: } catch (Throwable e) {
329: log.error(sm.getString("jaasRealm.unexpectedError"), e);
330: return (null);
331: }
332:
333: if (log.isDebugEnabled())
334: log.debug("Getting principal " + subject);
335:
336: // Return the appropriate Principal for this authenticated Subject
337: Principal principal = createPrincipal(username, subject);
338: if (principal == null) {
339: log.debug(sm.getString("jaasRealm.authenticateFailure",
340: username));
341: return (null);
342: }
343: if (log.isDebugEnabled()) {
344: log.debug(sm.getString("jaasRealm.authenticateSuccess",
345: username));
346: }
347:
348: return (principal);
349: } catch (Throwable t) {
350: log.error("error ", t);
351: return null;
352: }
353: }
354:
355: // -------------------------------------------------------- Package Methods
356:
357: // ------------------------------------------------------ Protected Methods
358:
359: /**
360: * Return a short name for this Realm implementation.
361: */
362: protected String getName() {
363:
364: return (name);
365:
366: }
367:
368: /**
369: * Return the password associated with the given principal's user name.
370: */
371: protected String getPassword(String username) {
372:
373: return (null);
374:
375: }
376:
377: /**
378: * Return the Principal associated with the given user name.
379: */
380: protected Principal getPrincipal(String username) {
381:
382: return (null);
383:
384: }
385:
386: /**
387: * Construct and return a <code>java.security.Principal</code> instance
388: * representing the authenticated user for the specified Subject. If no
389: * such Principal can be constructed, return <code>null</code>.
390: *
391: * @param subject The Subject representing the logged in user
392: */
393: protected Principal createPrincipal(String username, Subject subject) {
394: // Prepare to scan the Principals for this Subject
395: String password = null; // Will not be carried forward
396: ArrayList roles = new ArrayList();
397:
398: // Scan the Principals for this Subject
399: Iterator principals = subject.getPrincipals().iterator();
400: while (principals.hasNext()) {
401: Principal principal = (Principal) principals.next();
402: // No need to look further - that's our own stuff
403: if (principal instanceof GenericPrincipal) {
404: if (log.isDebugEnabled())
405: log
406: .debug("Found old GenericPrincipal "
407: + principal);
408: return principal;
409: }
410: String principalClass = principal.getClass().getName();
411: if (log.isDebugEnabled())
412: log.info("Principal: " + principalClass + " "
413: + principal);
414:
415: if (userClasses.contains(principalClass)) {
416: // Override the default - which is the original user, accepted by
417: // the friendly LoginManager
418: username = principal.getName();
419: }
420: if (roleClasses.contains(principalClass)) {
421: roles.add(principal.getName());
422: }
423: // Same as Jboss - that's a pretty clean solution
424: if ((principal instanceof Group)
425: && "Roles".equals(principal.getName())) {
426: Group grp = (Group) principal;
427: Enumeration en = grp.members();
428: while (en.hasMoreElements()) {
429: Principal roleP = (Principal) en.nextElement();
430: roles.add(roleP.getName());
431: }
432:
433: }
434: }
435:
436: // Create the resulting Principal for our authenticated user
437: if (username != null) {
438: return (new GenericPrincipal(this , username, password,
439: roles));
440: } else {
441: return (null);
442: }
443: }
444:
445: // ------------------------------------------------------ Lifecycle Methods
446:
447: /**
448: *
449: * Prepare for active use of the public methods of this Component.
450: *
451: * @exception LifecycleException if this component detects a fatal error
452: * that prevents it from being started
453: */
454: public void start() throws LifecycleException {
455:
456: // Perform normal superclass initialization
457: super .start();
458:
459: }
460:
461: /**
462: * Gracefully shut down active use of the public methods of this Component.
463: *
464: * @exception LifecycleException if this component detects a fatal error
465: * that needs to be reported
466: */
467: public void stop() throws LifecycleException {
468:
469: // Perform normal superclass finalization
470: super.stop();
471:
472: }
473:
474: }
|