KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > realm > JAASRealm


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 JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26
27 import javax.security.auth.Subject JavaDoc;
28 import javax.security.auth.login.AccountExpiredException JavaDoc;
29 import javax.security.auth.login.CredentialExpiredException JavaDoc;
30 import javax.security.auth.login.FailedLoginException JavaDoc;
31 import javax.security.auth.login.LoginContext JavaDoc;
32 import javax.security.auth.login.LoginException JavaDoc;
33
34 import org.apache.catalina.Container;
35 import org.apache.catalina.LifecycleException;
36 import org.apache.catalina.util.StringManager;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.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: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
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 JavaDoc appName = null;
139
140
141     /**
142      * Descriptive information about this <code>Realm</code> implementation.
143      */

144     protected static final String JavaDoc 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 JavaDoc name = "JAASRealm";
152
153
154     /**
155      * The list of role class names, split out for easy processing.
156      */

157     protected List JavaDoc roleClasses = new ArrayList JavaDoc();
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 JavaDoc userClasses = new ArrayList JavaDoc();
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 JavaDoc name) {
189         appName = name;
190     }
191     
192     /**
193      * getter for the <code>appName</code> member variable
194      */

195     public String JavaDoc 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 JavaDoc 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 JavaDoc roleClassNames = null;
238
239     public String JavaDoc getRoleClassNames() {
240         return (this.roleClassNames);
241     }
242
243      /**
244       * Sets the list of comma-delimited classes that represent
245       * roles. The classes in the list must implement <code>java.security.Principal</code>.
246       * When this accessor is called (for example, by a <code>Digester</code>
247       * instance parsing the
248       * configuration file), it will parse the class names and store the resulting
249       * string(s) into the <code>ArrayList</code> field </code>roleClasses</code>.
250       */

251      public void setRoleClassNames(String JavaDoc roleClassNames) {
252          this.roleClassNames = roleClassNames;
253         roleClasses.clear();
254         String JavaDoc temp = this.roleClassNames;
255         if (temp == null) {
256             return;
257         }
258         while (true) {
259             int comma = temp.indexOf(',');
260             if (comma < 0) {
261                 break;
262             }
263             roleClasses.add(temp.substring(0, comma).trim());
264             temp = temp.substring(comma + 1);
265         }
266         temp = temp.trim();
267         if (temp.length() > 0) {
268             roleClasses.add(temp);
269         }
270     }
271
272
273     /**
274      * Comma-delimited list of <code>java.security.Principal</code> classes
275      * that represent individual users.
276      */

277     protected String JavaDoc userClassNames = null;
278
279     public String JavaDoc getUserClassNames() {
280         return (this.userClassNames);
281     }
282
283      /**
284      * Sets the list of comma-delimited classes that represent individual
285      * users. The classes in the list must implement <code>java.security.Principal</code>.
286      * When this accessor is called (for example, by a <code>Digester</code>
287      * instance parsing the
288      * configuration file), it will parse the class names and store the resulting
289      * string(s) into the <code>ArrayList</code> field </code>userClasses</code>.
290      */

291     public void setUserClassNames(String JavaDoc userClassNames) {
292         this.userClassNames = userClassNames;
293         userClasses.clear();
294         String JavaDoc temp = this.userClassNames;
295         if (temp == null) {
296             return;
297         }
298         while (true) {
299             int comma = temp.indexOf(',');
300             if (comma < 0) {
301                 break;
302             }
303             userClasses.add(temp.substring(0, comma).trim());
304             temp = temp.substring(comma + 1);
305         }
306         temp = temp.trim();
307         if (temp.length() > 0) {
308             userClasses.add(temp);
309         }
310     }
311
312
313     // --------------------------------------------------------- Public Methods
314

315
316     /**
317      * Return the <code>Principal</code> associated with the specified username and
318      * credentials, if there is one; otherwise return <code>null</code>.
319      *
320      * If there are any errors with the JDBC connection, executing
321      * the query or anything we return null (don't authenticate). This
322      * event is also logged, and the connection will be closed so that
323      * a subsequent request will automatically re-open it.
324      *
325      * @param username Username of the <code>Principal</code> to look up
326      * @param credentials Password or other credentials to use in
327      * authenticating this username
328      */

329     public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc credentials) {
330
331         // Establish a LoginContext to use for authentication
332
try {
333         LoginContext JavaDoc loginContext = null;
334         if( appName==null ) appName="Tomcat";
335
336         if( log.isDebugEnabled())
337             log.debug(sm.getString("jaasRealm.beginLogin", username, appName));
338
339         // What if the LoginModule is in the container class loader ?
340
ClassLoader JavaDoc ocl = null;
341
342         if (isUseContextClassLoader()) {
343           ocl=Thread.currentThread().getContextClassLoader();
344           Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
345         }
346
347         try {
348             loginContext = new LoginContext JavaDoc
349                 (appName, new JAASCallbackHandler(this, username,
350                                                   credentials));
351         } catch (Throwable JavaDoc e) {
352             log.error(sm.getString("jaasRealm.unexpectedError"), e);
353             return (null);
354         } finally {
355             if( isUseContextClassLoader()) {
356               Thread.currentThread().setContextClassLoader(ocl);
357             }
358         }
359
360         if( log.isDebugEnabled())
361             log.debug("Login context created " + username);
362
363         // Negotiate a login via this LoginContext
364
Subject JavaDoc subject = null;
365         try {
366             loginContext.login();
367             subject = loginContext.getSubject();
368             if (subject == null) {
369                 if( log.isDebugEnabled())
370                     log.debug(sm.getString("jaasRealm.failedLogin", username));
371                 return (null);
372             }
373         } catch (AccountExpiredException JavaDoc e) {
374             if (log.isDebugEnabled())
375                 log.debug(sm.getString("jaasRealm.accountExpired", username));
376             return (null);
377         } catch (CredentialExpiredException JavaDoc e) {
378             if (log.isDebugEnabled())
379                 log.debug(sm.getString("jaasRealm.credentialExpired", username));
380             return (null);
381         } catch (FailedLoginException JavaDoc e) {
382             if (log.isDebugEnabled())
383                 log.debug(sm.getString("jaasRealm.failedLogin", username));
384             return (null);
385         } catch (LoginException JavaDoc e) {
386             log.warn(sm.getString("jaasRealm.loginException", username), e);
387             return (null);
388         } catch (Throwable JavaDoc e) {
389             log.error(sm.getString("jaasRealm.unexpectedError"), e);
390             return (null);
391         }
392
393         if( log.isDebugEnabled())
394             log.debug(sm.getString("jaasRealm.loginContextCreated", username));
395
396         // Return the appropriate Principal for this authenticated Subject
397
Principal JavaDoc principal = createPrincipal(username, subject);
398         if (principal == null) {
399             log.debug(sm.getString("jaasRealm.authenticateFailure", username));
400             return (null);
401         }
402         if (log.isDebugEnabled()) {
403             log.debug(sm.getString("jaasRealm.authenticateSuccess", username));
404         }
405
406         return (principal);
407         } catch( Throwable JavaDoc t) {
408             log.error( "error ", t);
409             return null;
410         }
411     }
412      
413
414     // -------------------------------------------------------- Package Methods
415

416
417     // ------------------------------------------------------ Protected Methods
418

419
420     /**
421      * Return a short name for this <code>Realm</code> implementation.
422      */

423     protected String JavaDoc getName() {
424
425         return (name);
426
427     }
428
429
430     /**
431      * Return the password associated with the given principal's user name.
432      */

433     protected String JavaDoc getPassword(String JavaDoc username) {
434
435         return (null);
436
437     }
438
439
440     /**
441      * Return the <code>Principal</code> associated with the given user name.
442      */

443     protected Principal JavaDoc getPrincipal(String JavaDoc username) {
444
445         return (null);
446
447     }
448
449
450     /**
451      * Identify and return a <code>java.security.Principal</code> instance
452      * representing the authenticated user for the specified <code>Subject</code>.
453      * The Principal is constructed by scanning the list of Principals returned
454      * by the JAASLoginModule. The first <code>Principal</code> object that matches
455      * one of the class names supplied as a "user class" is the user Principal.
456      * This object is returned to tha caller.
457      * Any remaining principal objects returned by the LoginModules are mapped to
458      * roles, but only if their respective classes match one of the "role class" classes.
459      * If a user Principal cannot be constructed, return <code>null</code>.
460      * @param subject The <code>Subject</code> representing the logged-in user
461      */

462     protected Principal JavaDoc createPrincipal(String JavaDoc username, Subject JavaDoc subject) {
463         // Prepare to scan the Principals for this Subject
464
String JavaDoc password = null; // Will not be carried forward
465

466         List JavaDoc roles = new ArrayList JavaDoc();
467         Principal JavaDoc userPrincipal = null;
468
469         // Scan the Principals for this Subject
470
Iterator JavaDoc principals = subject.getPrincipals().iterator();
471         while (principals.hasNext()) {
472             Principal JavaDoc principal = (Principal JavaDoc) principals.next();
473
474             String JavaDoc principalClass = principal.getClass().getName();
475
476             if( log.isDebugEnabled() ) {
477                 log.debug(sm.getString("jaasRealm.checkPrincipal", principal, principalClass));
478             }
479
480             if (userPrincipal == null && userClasses.contains(principalClass)) {
481                 userPrincipal = principal;
482                 if( log.isDebugEnabled() ) {
483                     log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName()));
484                 }
485             }
486             
487             if (roleClasses.contains(principalClass)) {
488                 roles.add(principal.getName());
489                 if( log.isDebugEnabled() ) {
490                     log.debug(sm.getString("jaasRealm.rolePrincipalAdd", principal.getName()));
491                 }
492             }
493         }
494
495         // Print failure message if needed
496
if (userPrincipal == null) {
497             if (log.isDebugEnabled()) {
498                 log.debug(sm.getString("jaasRealm.userPrincipalFailure"));
499                 log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
500             }
501         } else {
502             if (roles.size() == 0) {
503                 if (log.isDebugEnabled()) {
504                     log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
505                 }
506             }
507         }
508
509         // Return the resulting Principal for our authenticated user
510
return new GenericPrincipal(this, username, null, roles, userPrincipal);
511     }
512
513      /**
514       * Ensure the given name is legal for JAAS configuration.
515       * Added for Bugzilla 30869, made protected for easy customization
516       * in case my implementation is insufficient, which I think is
517       * very likely.
518       *
519       * @param src The name to validate
520       * @return A string that's a valid JAAS realm name
521       */

522      protected String JavaDoc makeLegalForJAAS(final String JavaDoc src) {
523          String JavaDoc result = src;
524          
525          // Default name is "other" per JAAS spec
526
if(result == null) {
527              result = "other";
528          }
529
530          // Strip leading slash if present, as Sun JAAS impl
531
// barfs on it (see Bugzilla 30869 bug report).
532
if(result.startsWith("/")) {
533              result = result.substring(1);
534          }
535
536          return result;
537      }
538
539
540     // ------------------------------------------------------ Lifecycle Methods
541

542
543     /**
544      *
545      * Prepare for active use of the public methods of this <code>Component</code>.
546      *
547      * @exception LifecycleException if this component detects a fatal error
548      * that prevents it from being started
549      */

550     public void start() throws LifecycleException {
551
552         // Perform normal superclass initialization
553
super.start();
554
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 }
573
Popular Tags