KickJava   Java API By Example, From Geeks To Geeks.

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


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.sql.Connection JavaDoc;
24 import java.sql.Driver JavaDoc;
25 import java.sql.PreparedStatement JavaDoc;
26 import java.sql.ResultSet JavaDoc;
27 import java.sql.SQLException JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Properties JavaDoc;
30
31 import org.apache.catalina.LifecycleException;
32 import org.apache.catalina.util.StringManager;
33
34
35 /**
36 *
37 * Implmentation of <b>Realm</b> that works with any JDBC supported database.
38 * See the JDBCRealm.howto for more details on how to set up the database and
39 * for configuration options.
40 *
41 * <p><strong>TODO</strong> - Support connection pooling (including message
42 * format objects) so that <code>authenticate()</code> does not have to be
43 * synchronized and would fix the ugly connection logic. </p>
44 *
45 * @author Craig R. McClanahan
46 * @author Carson McDonald
47 * @author Ignacio Ortega
48 * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
49 */

50
51 public class JDBCRealm
52     extends RealmBase {
53
54
55     // ----------------------------------------------------- Instance Variables
56

57
58     /**
59      * The connection username to use when trying to connect to the database.
60      */

61     protected String JavaDoc connectionName = null;
62
63
64     /**
65      * The connection URL to use when trying to connect to the database.
66      */

67     protected String JavaDoc connectionPassword = null;
68
69
70     /**
71      * The connection URL to use when trying to connect to the database.
72      */

73     protected String JavaDoc connectionURL = null;
74
75
76     /**
77      * The connection to the database.
78      */

79     protected Connection JavaDoc dbConnection = null;
80
81
82     /**
83      * Instance of the JDBC Driver class we use as a connection factory.
84      */

85     protected Driver JavaDoc driver = null;
86
87
88     /**
89      * The JDBC driver to use.
90      */

91     protected String JavaDoc driverName = null;
92
93
94     /**
95      * Descriptive information about this Realm implementation.
96      */

97     protected static final String JavaDoc info =
98         "org.apache.catalina.realm.JDBCRealm/1.0";
99
100
101     /**
102      * Descriptive information about this Realm implementation.
103      */

104     protected static final String JavaDoc name = "JDBCRealm";
105
106
107     /**
108      * The PreparedStatement to use for authenticating users.
109      */

110     protected PreparedStatement JavaDoc preparedCredentials = null;
111
112
113     /**
114      * The PreparedStatement to use for identifying the roles for
115      * a specified user.
116      */

117     protected PreparedStatement JavaDoc preparedRoles = null;
118
119
120     /**
121      * The column in the user role table that names a role
122      */

123     protected String JavaDoc roleNameCol = null;
124
125
126     /**
127      * The string manager for this package.
128      */

129     protected static final StringManager sm =
130         StringManager.getManager(Constants.Package);
131
132
133     /**
134      * The column in the user table that holds the user's credintials
135      */

136     protected String JavaDoc userCredCol = null;
137
138
139     /**
140      * The column in the user table that holds the user's name
141      */

142     protected String JavaDoc userNameCol = null;
143
144
145     /**
146      * The table that holds the relation between user's and roles
147      */

148     protected String JavaDoc userRoleTable = null;
149
150
151     /**
152      * The table that holds user data.
153      */

154     protected String JavaDoc userTable = null;
155
156
157     // ------------------------------------------------------------- Properties
158

159     /**
160      * Return the username to use to connect to the database.
161      *
162      */

163     public String JavaDoc getConnectionName() {
164         return connectionName;
165     }
166
167     /**
168      * Set the username to use to connect to the database.
169      *
170      * @param connectionName Username
171      */

172     public void setConnectionName(String JavaDoc connectionName) {
173         this.connectionName = connectionName;
174     }
175
176     /**
177      * Return the password to use to connect to the database.
178      *
179      */

180     public String JavaDoc getConnectionPassword() {
181         return connectionPassword;
182     }
183
184     /**
185      * Set the password to use to connect to the database.
186      *
187      * @param connectionPassword User password
188      */

189     public void setConnectionPassword(String JavaDoc connectionPassword) {
190         this.connectionPassword = connectionPassword;
191     }
192
193     /**
194      * Return the URL to use to connect to the database.
195      *
196      */

197     public String JavaDoc getConnectionURL() {
198         return connectionURL;
199     }
200
201     /**
202      * Set the URL to use to connect to the database.
203      *
204      * @param connectionURL The new connection URL
205      */

206     public void setConnectionURL( String JavaDoc connectionURL ) {
207       this.connectionURL = connectionURL;
208     }
209
210     /**
211      * Return the JDBC driver that will be used.
212      *
213      */

214     public String JavaDoc getDriverName() {
215         return driverName;
216     }
217
218     /**
219      * Set the JDBC driver that will be used.
220      *
221      * @param driverName The driver name
222      */

223     public void setDriverName( String JavaDoc driverName ) {
224       this.driverName = driverName;
225     }
226
227     /**
228      * Return the column in the user role table that names a role.
229      *
230      */

231     public String JavaDoc getRoleNameCol() {
232         return roleNameCol;
233     }
234
235     /**
236      * Set the column in the user role table that names a role.
237      *
238      * @param roleNameCol The column name
239      */

240     public void setRoleNameCol( String JavaDoc roleNameCol ) {
241         this.roleNameCol = roleNameCol;
242     }
243
244     /**
245      * Return the column in the user table that holds the user's credentials.
246      *
247      */

248     public String JavaDoc getUserCredCol() {
249         return userCredCol;
250     }
251
252     /**
253      * Set the column in the user table that holds the user's credentials.
254      *
255      * @param userCredCol The column name
256      */

257     public void setUserCredCol( String JavaDoc userCredCol ) {
258        this.userCredCol = userCredCol;
259     }
260
261     /**
262      * Return the column in the user table that holds the user's name.
263      *
264      */

265     public String JavaDoc getUserNameCol() {
266         return userNameCol;
267     }
268
269     /**
270      * Set the column in the user table that holds the user's name.
271      *
272      * @param userNameCol The column name
273      */

274     public void setUserNameCol( String JavaDoc userNameCol ) {
275        this.userNameCol = userNameCol;
276     }
277
278     /**
279      * Return the table that holds the relation between user's and roles.
280      *
281      */

282     public String JavaDoc getUserRoleTable() {
283         return userRoleTable;
284     }
285
286     /**
287      * Set the table that holds the relation between user's and roles.
288      *
289      * @param userRoleTable The table name
290      */

291     public void setUserRoleTable( String JavaDoc userRoleTable ) {
292         this.userRoleTable = userRoleTable;
293     }
294
295     /**
296      * Return the table that holds user data..
297      *
298      */

299     public String JavaDoc getUserTable() {
300         return userTable;
301     }
302
303     /**
304      * Set the table that holds user data.
305      *
306      * @param userTable The table name
307      */

308     public void setUserTable( String JavaDoc userTable ) {
309       this.userTable = userTable;
310     }
311
312
313     // --------------------------------------------------------- Public Methods
314

315
316     /**
317      * Return the Principal 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      *
326      * @param username Username of the Principal to look up
327      * @param credentials Password or other credentials to use in
328      * authenticating this username
329      */

330     public synchronized Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc credentials) {
331
332         // Number of tries is the numebr of attempts to connect to the database
333
// during this login attempt (if we need to open the database)
334
// This needs rewritten wuth better pooling support, the existing code
335
// needs signature changes since the Prepared statements needs cached
336
// with the connections.
337
// The code below will try twice if there is a SQLException so the
338
// connection may try to be opened again. On normal conditions (including
339
// invalid login - the above is only used once.
340
int numberOfTries = 2;
341         while (numberOfTries>0) {
342             try {
343
344                 // Ensure that we have an open database connection
345
open();
346
347                 // Acquire a Principal object for this user
348
Principal JavaDoc principal = authenticate(dbConnection,
349                                                    username, credentials);
350
351
352                 // Return the Principal (if any)
353
return (principal);
354
355             } catch (SQLException JavaDoc e) {
356
357                 // Log the problem for posterity
358
containerLog.error(sm.getString("jdbcRealm.exception"), e);
359
360                 // Close the connection so that it gets reopened next time
361
if (dbConnection != null)
362                     close(dbConnection);
363
364             }
365
366             numberOfTries--;
367         }
368
369         // Worst case scenario
370
return null;
371
372     }
373
374
375     // -------------------------------------------------------- Package Methods
376

377
378     // ------------------------------------------------------ Protected Methods
379

380
381     /**
382      * Return the Principal associated with the specified username and
383      * credentials, if there is one; otherwise return <code>null</code>.
384      *
385      * @param dbConnection The database connection to be used
386      * @param username Username of the Principal to look up
387      * @param credentials Password or other credentials to use in
388      * authenticating this username
389      */

390     public synchronized Principal JavaDoc authenticate(Connection JavaDoc dbConnection,
391                                                String JavaDoc username,
392                                                String JavaDoc credentials) {
393
394         // No user - can't possibly authenticate
395
if (username == null) {
396             return (null);
397         }
398
399         // Look up the user's credentials
400
String JavaDoc dbCredentials = getPassword(username);
401
402         // Validate the user's credentials
403
boolean validated = false;
404         if (hasMessageDigest()) {
405             // Hex hashes should be compared case-insensitive
406
validated = (digest(credentials).equalsIgnoreCase(dbCredentials));
407         } else {
408             validated = (digest(credentials).equals(dbCredentials));
409         }
410
411         if (validated) {
412             if (containerLog.isTraceEnabled())
413                 containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess",
414                                                 username));
415         } else {
416             if (containerLog.isTraceEnabled())
417                 containerLog.trace(sm.getString("jdbcRealm.authenticateFailure",
418                                                 username));
419             return (null);
420         }
421
422         ArrayList JavaDoc roles = getRoles(username);
423         
424         // Create and return a suitable Principal for this user
425
return (new GenericPrincipal(this, username, credentials, roles));
426
427     }
428
429
430     /**
431      * Close the specified database connection.
432      *
433      * @param dbConnection The connection to be closed
434      */

435     protected void close(Connection JavaDoc dbConnection) {
436
437         // Do nothing if the database connection is already closed
438
if (dbConnection == null)
439             return;
440
441         // Close our prepared statements (if any)
442
try {
443             preparedCredentials.close();
444         } catch (Throwable JavaDoc f) {
445             ;
446         }
447         this.preparedCredentials = null;
448
449
450         try {
451             preparedRoles.close();
452         } catch (Throwable JavaDoc f) {
453             ;
454         }
455         this.preparedRoles = null;
456
457
458         // Close this database connection, and log any errors
459
try {
460             dbConnection.close();
461         } catch (SQLException JavaDoc e) {
462             containerLog.warn(sm.getString("jdbcRealm.close"), e); // Just log it here
463
} finally {
464            this.dbConnection = null;
465         }
466
467     }
468
469
470     /**
471      * Return a PreparedStatement configured to perform the SELECT required
472      * to retrieve user credentials for the specified username.
473      *
474      * @param dbConnection The database connection to be used
475      * @param username Username for which credentials should be retrieved
476      *
477      * @exception SQLException if a database error occurs
478      */

479     protected PreparedStatement JavaDoc credentials(Connection JavaDoc dbConnection,
480                                             String JavaDoc username)
481         throws SQLException JavaDoc {
482
483         if (preparedCredentials == null) {
484             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("SELECT ");
485             sb.append(userCredCol);
486             sb.append(" FROM ");
487             sb.append(userTable);
488             sb.append(" WHERE ");
489             sb.append(userNameCol);
490             sb.append(" = ?");
491
492             if(containerLog.isDebugEnabled()) {
493                 containerLog.debug("credentials query: " + sb.toString());
494             }
495
496             preparedCredentials =
497                 dbConnection.prepareStatement(sb.toString());
498         }
499
500         if (username == null) {
501             preparedCredentials.setNull(1,java.sql.Types.VARCHAR);
502         } else {
503             preparedCredentials.setString(1, username);
504         }
505
506         return (preparedCredentials);
507     }
508
509
510     /**
511      * Return a short name for this Realm implementation.
512      */

513     protected String JavaDoc getName() {
514
515         return (name);
516
517     }
518
519
520     /**
521      * Return the password associated with the given principal's user name.
522      */

523     protected String JavaDoc getPassword(String JavaDoc username) {
524
525         // Look up the user's credentials
526
String JavaDoc dbCredentials = null;
527         PreparedStatement JavaDoc stmt = null;
528         ResultSet JavaDoc rs = null;
529
530         // Number of tries is the numebr of attempts to connect to the database
531
// during this login attempt (if we need to open the database)
532
// This needs rewritten wuth better pooling support, the existing code
533
// needs signature changes since the Prepared statements needs cached
534
// with the connections.
535
// The code below will try twice if there is a SQLException so the
536
// connection may try to be opened again. On normal conditions (including
537
// invalid login - the above is only used once.
538
int numberOfTries = 2;
539         while (numberOfTries>0) {
540             try {
541                 
542                 // Ensure that we have an open database connection
543
open();
544                 
545                 try {
546                     stmt = credentials(dbConnection, username);
547                     rs = stmt.executeQuery();
548                     
549                     if (rs.next()) {
550                         dbCredentials = rs.getString(1);
551                     }
552                     rs.close();
553                     rs = null;
554                     if (dbCredentials == null) {
555                         return (null);
556                     }
557                     
558                     dbCredentials = dbCredentials.trim();
559                     return dbCredentials;
560                     
561                 } finally {
562                     if (rs!=null) {
563                         try {
564                             rs.close();
565                         } catch(SQLException JavaDoc e) {
566                             containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
567                         }
568                     }
569                     dbConnection.commit();
570                 }
571                 
572             } catch (SQLException JavaDoc e) {
573                 
574                 // Log the problem for posterity
575
containerLog.error(sm.getString("jdbcRealm.exception"), e);
576                 
577                 // Close the connection so that it gets reopened next time
578
if (dbConnection != null)
579                     close(dbConnection);
580                 
581             }
582             
583             numberOfTries--;
584         }
585         
586         return (null);
587     }
588
589
590     /**
591      * Return the Principal associated with the given user name.
592      */

593     protected Principal JavaDoc getPrincipal(String JavaDoc username) {
594
595         return (new GenericPrincipal(this,
596                                      username,
597                                      getPassword(username),
598                                      getRoles(username)));
599
600     }
601
602
603     /**
604      * Return the roles associated with the gven user name.
605      */

606     protected ArrayList JavaDoc getRoles(String JavaDoc username) {
607         
608         PreparedStatement JavaDoc stmt = null;
609         ResultSet JavaDoc rs = null;
610
611         // Number of tries is the numebr of attempts to connect to the database
612
// during this login attempt (if we need to open the database)
613
// This needs rewritten wuth better pooling support, the existing code
614
// needs signature changes since the Prepared statements needs cached
615
// with the connections.
616
// The code below will try twice if there is a SQLException so the
617
// connection may try to be opened again. On normal conditions (including
618
// invalid login - the above is only used once.
619
int numberOfTries = 2;
620         while (numberOfTries>0) {
621             try {
622                 
623                 // Ensure that we have an open database connection
624
open();
625                 
626                 try {
627                     // Accumulate the user's roles
628
ArrayList JavaDoc roleList = new ArrayList JavaDoc();
629                     stmt = roles(dbConnection, username);
630                     rs = stmt.executeQuery();
631                     while (rs.next()) {
632                         String JavaDoc role = rs.getString(1);
633                         if (null!=role) {
634                             roleList.add(role.trim());
635                         }
636                     }
637                     rs.close();
638                     rs = null;
639                     
640                     return (roleList);
641                     
642                 } finally {
643                     if (rs!=null) {
644                         try {
645                             rs.close();
646                         } catch(SQLException JavaDoc e) {
647                             containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
648                         }
649                     }
650                     dbConnection.commit();
651                 }
652                 
653             } catch (SQLException JavaDoc e) {
654                 
655                 // Log the problem for posterity
656
containerLog.error(sm.getString("jdbcRealm.exception"), e);
657                 
658                 // Close the connection so that it gets reopened next time
659
if (dbConnection != null)
660                     close(dbConnection);
661                 
662             }
663             
664             numberOfTries--;
665         }
666         
667         return (null);
668         
669     }
670     
671     
672     /**
673      * Open (if necessary) and return a database connection for use by
674      * this Realm.
675      *
676      * @exception SQLException if a database error occurs
677      */

678     protected Connection JavaDoc open() throws SQLException JavaDoc {
679
680         // Do nothing if there is a database connection already open
681
if (dbConnection != null)
682             return (dbConnection);
683
684         // Instantiate our database driver if necessary
685
if (driver == null) {
686             try {
687                 Class JavaDoc clazz = Class.forName(driverName);
688                 driver = (Driver JavaDoc) clazz.newInstance();
689             } catch (Throwable JavaDoc e) {
690                 throw new SQLException JavaDoc(e.getMessage());
691             }
692         }
693
694         // Open a new connection
695
Properties JavaDoc props = new Properties JavaDoc();
696         if (connectionName != null)
697             props.put("user", connectionName);
698         if (connectionPassword != null)
699             props.put("password", connectionPassword);
700         dbConnection = driver.connect(connectionURL, props);
701         dbConnection.setAutoCommit(false);
702         return (dbConnection);
703
704     }
705
706
707     /**
708      * Release our use of this connection so that it can be recycled.
709      *
710      * @param dbConnection The connection to be released
711      */

712     protected void release(Connection JavaDoc dbConnection) {
713
714         ; // NO-OP since we are not pooling anything
715

716     }
717
718
719     /**
720      * Return a PreparedStatement configured to perform the SELECT required
721      * to retrieve user roles for the specified username.
722      *
723      * @param dbConnection The database connection to be used
724      * @param username Username for which roles should be retrieved
725      *
726      * @exception SQLException if a database error occurs
727      */

728     protected PreparedStatement JavaDoc roles(Connection JavaDoc dbConnection, String JavaDoc username)
729         throws SQLException JavaDoc {
730
731         if (preparedRoles == null) {
732             StringBuffer JavaDoc sb = new StringBuffer JavaDoc("SELECT ");
733             sb.append(roleNameCol);
734             sb.append(" FROM ");
735             sb.append(userRoleTable);
736             sb.append(" WHERE ");
737             sb.append(userNameCol);
738             sb.append(" = ?");
739             preparedRoles =
740                 dbConnection.prepareStatement(sb.toString());
741         }
742
743         preparedRoles.setString(1, username);
744         return (preparedRoles);
745
746     }
747
748
749     // ------------------------------------------------------ Lifecycle Methods
750

751
752     /**
753      *
754      * Prepare for active use of the public methods of this Component.
755      *
756      * @exception LifecycleException if this component detects a fatal error
757      * that prevents it from being started
758      */

759     public void start() throws LifecycleException {
760
761         // Perform normal superclass initialization
762
super.start();
763
764         // Validate that we can open our connection - but let tomcat
765
// startup in case the database is temporarily unavailable
766
try {
767             open();
768         } catch (SQLException JavaDoc e) {
769             containerLog.error(sm.getString("jdbcRealm.open"), e);
770         }
771
772     }
773
774
775     /**
776      * Gracefully shut down active use of the public methods of this Component.
777      *
778      * @exception LifecycleException if this component detects a fatal error
779      * that needs to be reported
780      */

781     public void stop() throws LifecycleException {
782
783         // Perform normal superclass finalization
784
super.stop();
785
786         // Close any open DB connection
787
close(this.dbConnection);
788
789     }
790
791
792 }
793
Popular Tags