Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]
    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;
   23   import java.sql.Connection;
   24   import java.sql.Driver;
   25   import java.sql.PreparedStatement;
   26   import java.sql.ResultSet;
   27   import java.sql.SQLException;
   28   import java.util.ArrayList;
   29   import java.util.Properties;
   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>,
   43   * <code>getPassword()</code> and <code>authenticate()</code> do not have to be
   44   * synchronized and would fix the ugly connection logic. </p>
   45   *
   46   * @author Craig R. McClanahan
   47   * @author Carson McDonald
   48   * @author Ignacio Ortega
   49   * @version $Revision: 543691 $ $Date: 2007-06-02 03:37:08 +0200 (sam., 02 juin 2007) $
   50   */
   51   
   52   public class JDBCRealm
   53       extends RealmBase {
   54   
   55   
   56       // ----------------------------------------------------- Instance Variables
   57   
   58   
   59       /**
   60        * The connection username to use when trying to connect to the database.
   61        */
   62       protected String connectionName = null;
   63   
   64   
   65       /**
   66        * The connection URL to use when trying to connect to the database.
   67        */
   68       protected String connectionPassword = null;
   69   
   70   
   71       /**
   72        * The connection URL to use when trying to connect to the database.
   73        */
   74       protected String connectionURL = null;
   75   
   76   
   77       /**
   78        * The connection to the database.
   79        */
   80       protected Connection dbConnection = null;
   81   
   82   
   83       /**
   84        * Instance of the JDBC Driver class we use as a connection factory.
   85        */
   86       protected Driver driver = null;
   87   
   88   
   89       /**
   90        * The JDBC driver to use.
   91        */
   92       protected String driverName = null;
   93   
   94   
   95       /**
   96        * Descriptive information about this Realm implementation.
   97        */
   98       protected static final String info =
   99           "org.apache.catalina.realm.JDBCRealm/1.0";
  100   
  101   
  102       /**
  103        * Descriptive information about this Realm implementation.
  104        */
  105       protected static final String name = "JDBCRealm";
  106   
  107   
  108       /**
  109        * The PreparedStatement to use for authenticating users.
  110        */
  111       protected PreparedStatement preparedCredentials = null;
  112   
  113   
  114       /**
  115        * The PreparedStatement to use for identifying the roles for
  116        * a specified user.
  117        */
  118       protected PreparedStatement preparedRoles = null;
  119   
  120   
  121       /**
  122        * The column in the user role table that names a role
  123        */
  124       protected String roleNameCol = null;
  125   
  126   
  127       /**
  128        * The string manager for this package.
  129        */
  130       protected static final StringManager sm =
  131           StringManager.getManager(Constants.Package);
  132   
  133   
  134       /**
  135        * The column in the user table that holds the user's credintials
  136        */
  137       protected String userCredCol = null;
  138   
  139   
  140       /**
  141        * The column in the user table that holds the user's name
  142        */
  143       protected String userNameCol = null;
  144   
  145   
  146       /**
  147        * The table that holds the relation between user's and roles
  148        */
  149       protected String userRoleTable = null;
  150   
  151   
  152       /**
  153        * The table that holds user data.
  154        */
  155       protected String userTable = null;
  156   
  157   
  158       // ------------------------------------------------------------- Properties
  159   
  160       /**
  161        * Return the username to use to connect to the database.
  162        *
  163        */
  164       public String getConnectionName() {
  165           return connectionName;
  166       }
  167   
  168       /**
  169        * Set the username to use to connect to the database.
  170        *
  171        * @param connectionName Username
  172        */
  173       public void setConnectionName(String connectionName) {
  174           this.connectionName = connectionName;
  175       }
  176   
  177       /**
  178        * Return the password to use to connect to the database.
  179        *
  180        */
  181       public String getConnectionPassword() {
  182           return connectionPassword;
  183       }
  184   
  185       /**
  186        * Set the password to use to connect to the database.
  187        *
  188        * @param connectionPassword User password
  189        */
  190       public void setConnectionPassword(String connectionPassword) {
  191           this.connectionPassword = connectionPassword;
  192       }
  193   
  194       /**
  195        * Return the URL to use to connect to the database.
  196        *
  197        */
  198       public String getConnectionURL() {
  199           return connectionURL;
  200       }
  201   
  202       /**
  203        * Set the URL to use to connect to the database.
  204        *
  205        * @param connectionURL The new connection URL
  206        */
  207       public void setConnectionURL( String connectionURL ) {
  208         this.connectionURL = connectionURL;
  209       }
  210   
  211       /**
  212        * Return the JDBC driver that will be used.
  213        *
  214        */
  215       public String getDriverName() {
  216           return driverName;
  217       }
  218   
  219       /**
  220        * Set the JDBC driver that will be used.
  221        *
  222        * @param driverName The driver name
  223        */
  224       public void setDriverName( String driverName ) {
  225         this.driverName = driverName;
  226       }
  227   
  228       /**
  229        * Return the column in the user role table that names a role.
  230        *
  231        */
  232       public String getRoleNameCol() {
  233           return roleNameCol;
  234       }
  235   
  236       /**
  237        * Set the column in the user role table that names a role.
  238        *
  239        * @param roleNameCol The column name
  240        */
  241       public void setRoleNameCol( String roleNameCol ) {
  242           this.roleNameCol = roleNameCol;
  243       }
  244   
  245       /**
  246        * Return the column in the user table that holds the user's credentials.
  247        *
  248        */
  249       public String getUserCredCol() {
  250           return userCredCol;
  251       }
  252   
  253       /**
  254        * Set the column in the user table that holds the user's credentials.
  255        *
  256        * @param userCredCol The column name
  257        */
  258       public void setUserCredCol( String userCredCol ) {
  259          this.userCredCol = userCredCol;
  260       }
  261   
  262       /**
  263        * Return the column in the user table that holds the user's name.
  264        *
  265        */
  266       public String getUserNameCol() {
  267           return userNameCol;
  268       }
  269   
  270       /**
  271        * Set the column in the user table that holds the user's name.
  272        *
  273        * @param userNameCol The column name
  274        */
  275       public void setUserNameCol( String userNameCol ) {
  276          this.userNameCol = userNameCol;
  277       }
  278   
  279       /**
  280        * Return the table that holds the relation between user's and roles.
  281        *
  282        */
  283       public String getUserRoleTable() {
  284           return userRoleTable;
  285       }
  286   
  287       /**
  288        * Set the table that holds the relation between user's and roles.
  289        *
  290        * @param userRoleTable The table name
  291        */
  292       public void setUserRoleTable( String userRoleTable ) {
  293           this.userRoleTable = userRoleTable;
  294       }
  295   
  296       /**
  297        * Return the table that holds user data..
  298        *
  299        */
  300       public String getUserTable() {
  301           return userTable;
  302       }
  303   
  304       /**
  305        * Set the table that holds user data.
  306        *
  307        * @param userTable The table name
  308        */
  309       public void setUserTable( String userTable ) {
  310         this.userTable = userTable;
  311       }
  312   
  313   
  314       // --------------------------------------------------------- Public Methods
  315   
  316   
  317       /**
  318        * Return the Principal associated with the specified username and
  319        * credentials, if there is one; otherwise return <code>null</code>.
  320        *
  321        * If there are any errors with the JDBC connection, executing
  322        * the query or anything we return null (don't authenticate). This
  323        * event is also logged, and the connection will be closed so that
  324        * a subsequent request will automatically re-open it.
  325        *
  326        *
  327        * @param username Username of the Principal to look up
  328        * @param credentials Password or other credentials to use in
  329        *  authenticating this username
  330        */
  331       public synchronized Principal authenticate(String username, String credentials) {
  332   
  333           // Number of tries is the numebr of attempts to connect to the database
  334           // during this login attempt (if we need to open the database)
  335           // This needs rewritten wuth better pooling support, the existing code
  336           // needs signature changes since the Prepared statements needs cached
  337           // with the connections.
  338           // The code below will try twice if there is a SQLException so the
  339           // connection may try to be opened again. On normal conditions (including
  340           // invalid login - the above is only used once.
  341           int numberOfTries = 2;
  342           while (numberOfTries>0) {
  343               try {
  344   
  345                   // Ensure that we have an open database connection
  346                   open();
  347   
  348                   // Acquire a Principal object for this user
  349                   Principal principal = authenticate(dbConnection,
  350                                                      username, credentials);
  351   
  352   
  353                   // Return the Principal (if any)
  354                   return (principal);
  355   
  356               } catch (SQLException e) {
  357   
  358                   // Log the problem for posterity
  359                   containerLog.error(sm.getString("jdbcRealm.exception"), e);
  360   
  361                   // Close the connection so that it gets reopened next time
  362                   if (dbConnection != null)
  363                       close(dbConnection);
  364   
  365               }
  366   
  367               numberOfTries--;
  368           }
  369   
  370           // Worst case scenario
  371           return null;
  372   
  373       }
  374   
  375   
  376       // -------------------------------------------------------- Package Methods
  377   
  378   
  379       // ------------------------------------------------------ Protected Methods
  380   
  381   
  382       /**
  383        * Return the Principal associated with the specified username and
  384        * credentials, if there is one; otherwise return <code>null</code>.
  385        *
  386        * @param dbConnection The database connection to be used
  387        * @param username Username of the Principal to look up
  388        * @param credentials Password or other credentials to use in
  389        *  authenticating this username
  390        */
  391       public synchronized Principal authenticate(Connection dbConnection,
  392                                                  String username,
  393                                                  String credentials) {
  394   
  395           // No user - can't possibly authenticate
  396           if (username == null) {
  397               return (null);
  398           }
  399   
  400           // Look up the user's credentials
  401           String dbCredentials = getPassword(username);
  402   
  403           // Validate the user's credentials
  404           boolean validated = false;
  405           if (hasMessageDigest()) {
  406               // Hex hashes should be compared case-insensitive
  407               validated = (digest(credentials).equalsIgnoreCase(dbCredentials));
  408           } else {
  409               validated = (digest(credentials).equals(dbCredentials));
  410           }
  411   
  412           if (validated) {
  413               if (containerLog.isTraceEnabled())
  414                   containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess",
  415                                                   username));
  416           } else {
  417               if (containerLog.isTraceEnabled())
  418                   containerLog.trace(sm.getString("jdbcRealm.authenticateFailure",
  419                                                   username));
  420               return (null);
  421           }
  422   
  423           ArrayList<String> roles = getRoles(username);
  424           
  425           // Create and return a suitable Principal for this user
  426           return (new GenericPrincipal(this, username, credentials, roles));
  427   
  428       }
  429   
  430   
  431       /**
  432        * Close the specified database connection.
  433        *
  434        * @param dbConnection The connection to be closed
  435        */
  436       protected void close(Connection dbConnection) {
  437   
  438           // Do nothing if the database connection is already closed
  439           if (dbConnection == null)
  440               return;
  441   
  442           // Close our prepared statements (if any)
  443           try {
  444               preparedCredentials.close();
  445           } catch (Throwable f) {
  446               ;
  447           }
  448           this.preparedCredentials = null;
  449   
  450   
  451           try {
  452               preparedRoles.close();
  453           } catch (Throwable f) {
  454               ;
  455           }
  456           this.preparedRoles = null;
  457   
  458   
  459           // Close this database connection, and log any errors
  460           try {
  461               dbConnection.close();
  462           } catch (SQLException e) {
  463               containerLog.warn(sm.getString("jdbcRealm.close"), e); // Just log it here
  464           } finally {
  465              this.dbConnection = null;
  466           }
  467   
  468       }
  469   
  470   
  471       /**
  472        * Return a PreparedStatement configured to perform the SELECT required
  473        * to retrieve user credentials for the specified username.
  474        *
  475        * @param dbConnection The database connection to be used
  476        * @param username Username for which credentials should be retrieved
  477        *
  478        * @exception SQLException if a database error occurs
  479        */
  480       protected PreparedStatement credentials(Connection dbConnection,
  481                                               String username)
  482           throws SQLException {
  483   
  484           if (preparedCredentials == null) {
  485               StringBuffer sb = new StringBuffer("SELECT ");
  486               sb.append(userCredCol);
  487               sb.append(" FROM ");
  488               sb.append(userTable);
  489               sb.append(" WHERE ");
  490               sb.append(userNameCol);
  491               sb.append(" = ?");
  492   
  493               if(containerLog.isDebugEnabled()) {
  494                   containerLog.debug("credentials query: " + sb.toString());
  495               }
  496   
  497               preparedCredentials =
  498                   dbConnection.prepareStatement(sb.toString());
  499           }
  500   
  501           if (username == null) {
  502               preparedCredentials.setNull(1,java.sql.Types.VARCHAR);
  503           } else {
  504               preparedCredentials.setString(1, username);
  505           }
  506   
  507           return (preparedCredentials);
  508       }
  509   
  510   
  511       /**
  512        * Return a short name for this Realm implementation.
  513        */
  514       protected String getName() {
  515   
  516           return (name);
  517   
  518       }
  519   
  520   
  521       /**
  522        * Return the password associated with the given principal's user name.
  523        */
  524       protected synchronized String getPassword(String username) {
  525   
  526           // Look up the user's credentials
  527           String dbCredentials = null;
  528           PreparedStatement stmt = null;
  529           ResultSet rs = null;
  530   
  531           // Number of tries is the numebr of attempts to connect to the database
  532           // during this login attempt (if we need to open the database)
  533           // This needs rewritten wuth better pooling support, the existing code
  534           // needs signature changes since the Prepared statements needs cached
  535           // with the connections.
  536           // The code below will try twice if there is a SQLException so the
  537           // connection may try to be opened again. On normal conditions (including
  538           // invalid login - the above is only used once.
  539           int numberOfTries = 2;
  540           while (numberOfTries>0) {
  541               try {
  542                   
  543                   // Ensure that we have an open database connection
  544                   open();
  545                   
  546                   try {
  547                       stmt = credentials(dbConnection, username);
  548                       rs = stmt.executeQuery();
  549                       
  550                       if (rs.next()) {
  551                           dbCredentials = rs.getString(1);
  552                       }
  553                       rs.close();
  554                       rs = null;
  555                       if (dbCredentials == null) {
  556                           return (null);
  557                       }
  558                       
  559                       dbCredentials = dbCredentials.trim();
  560                       return dbCredentials;
  561                       
  562                   } finally {
  563                       if (rs!=null) {
  564                           try {
  565                               rs.close();
  566                           } catch(SQLException e) {
  567                               containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
  568                           }
  569                       }
  570                       dbConnection.commit();
  571                   }
  572                   
  573               } catch (SQLException e) {
  574                   
  575                   // Log the problem for posterity
  576                   containerLog.error(sm.getString("jdbcRealm.exception"), e);
  577                   
  578                   // Close the connection so that it gets reopened next time
  579                   if (dbConnection != null)
  580                       close(dbConnection);
  581                   
  582               }
  583               
  584               numberOfTries--;
  585           }
  586           
  587           return (null);
  588       }
  589   
  590   
  591       /**
  592        * Return the Principal associated with the given user name.
  593        */
  594       protected Principal getPrincipal(String username) {
  595   
  596           return (new GenericPrincipal(this,
  597                                        username,
  598                                        getPassword(username),
  599                                        getRoles(username)));
  600   
  601       }
  602   
  603   
  604       /**
  605        * Return the roles associated with the gven user name.
  606        */
  607       protected ArrayList<String> getRoles(String username) {
  608           
  609           PreparedStatement stmt = null;
  610           ResultSet rs = null;
  611   
  612           // Number of tries is the numebr of attempts to connect to the database
  613           // during this login attempt (if we need to open the database)
  614           // This needs rewritten wuth better pooling support, the existing code
  615           // needs signature changes since the Prepared statements needs cached
  616           // with the connections.
  617           // The code below will try twice if there is a SQLException so the
  618           // connection may try to be opened again. On normal conditions (including
  619           // invalid login - the above is only used once.
  620           int numberOfTries = 2;
  621           while (numberOfTries>0) {
  622               try {
  623                   
  624                   // Ensure that we have an open database connection
  625                   open();
  626                   
  627                   try {
  628                       // Accumulate the user's roles
  629                       ArrayList<String> roleList = new ArrayList<String>();
  630                       stmt = roles(dbConnection, username);
  631                       rs = stmt.executeQuery();
  632                       while (rs.next()) {
  633                           String role = rs.getString(1);
  634                           if (null!=role) {
  635                               roleList.add(role.trim());
  636                           }
  637                       }
  638                       rs.close();
  639                       rs = null;
  640                       
  641                       return (roleList);
  642                       
  643                   } finally {
  644                       if (rs!=null) {
  645                           try {
  646                               rs.close();
  647                           } catch(SQLException e) {
  648                               containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
  649                           }
  650                       }
  651                       dbConnection.commit();
  652                   }
  653                   
  654               } catch (SQLException e) {
  655                   
  656                   // Log the problem for posterity
  657                   containerLog.error(sm.getString("jdbcRealm.exception"), e);
  658                   
  659                   // Close the connection so that it gets reopened next time
  660                   if (dbConnection != null)
  661                       close(dbConnection);
  662                   
  663               }
  664               
  665               numberOfTries--;
  666           }
  667           
  668           return (null);
  669           
  670       }
  671       
  672       
  673       /**
  674        * Open (if necessary) and return a database connection for use by
  675        * this Realm.
  676        *
  677        * @exception SQLException if a database error occurs
  678        */
  679       protected Connection open() throws SQLException {
  680   
  681           // Do nothing if there is a database connection already open
  682           if (dbConnection != null)
  683               return (dbConnection);
  684   
  685           // Instantiate our database driver if necessary
  686           if (driver == null) {
  687               try {
  688                   Class clazz = Class.forName(driverName);
  689                   driver = (Driver) clazz.newInstance();
  690               } catch (Throwable e) {
  691                   throw new SQLException(e.getMessage());
  692               }
  693           }
  694   
  695           // Open a new connection
  696           Properties props = new Properties();
  697           if (connectionName != null)
  698               props.put("user", connectionName);
  699           if (connectionPassword != null)
  700               props.put("password", connectionPassword);
  701           dbConnection = driver.connect(connectionURL, props);
  702           dbConnection.setAutoCommit(false);
  703           return (dbConnection);
  704   
  705       }
  706   
  707   
  708       /**
  709        * Release our use of this connection so that it can be recycled.
  710        *
  711        * @param dbConnection The connection to be released
  712        */
  713       protected void release(Connection dbConnection) {
  714   
  715           ; // NO-OP since we are not pooling anything
  716   
  717       }
  718   
  719   
  720       /**
  721        * Return a PreparedStatement configured to perform the SELECT required
  722        * to retrieve user roles for the specified username.
  723        *
  724        * @param dbConnection The database connection to be used
  725        * @param username Username for which roles should be retrieved
  726        *
  727        * @exception SQLException if a database error occurs
  728        */
  729       protected synchronized PreparedStatement roles(Connection dbConnection,
  730               String username)
  731           throws SQLException {
  732   
  733           if (preparedRoles == null) {
  734               StringBuffer sb = new StringBuffer("SELECT ");
  735               sb.append(roleNameCol);
  736               sb.append(" FROM ");
  737               sb.append(userRoleTable);
  738               sb.append(" WHERE ");
  739               sb.append(userNameCol);
  740               sb.append(" = ?");
  741               preparedRoles =
  742                   dbConnection.prepareStatement(sb.toString());
  743           }
  744   
  745           preparedRoles.setString(1, username);
  746           return (preparedRoles);
  747   
  748       }
  749   
  750   
  751       // ------------------------------------------------------ Lifecycle Methods
  752   
  753   
  754       /**
  755        *
  756        * Prepare for active use of the public methods of this Component.
  757        *
  758        * @exception LifecycleException if this component detects a fatal error
  759        *  that prevents it from being started
  760        */
  761       public void start() throws LifecycleException {
  762   
  763           // Perform normal superclass initialization
  764           super.start();
  765   
  766           // Validate that we can open our connection - but let tomcat
  767           // startup in case the database is temporarily unavailable
  768           try {
  769               open();
  770           } catch (SQLException e) {
  771               containerLog.error(sm.getString("jdbcRealm.open"), e);
  772           }
  773   
  774       }
  775   
  776   
  777       /**
  778        * Gracefully shut down active use of the public methods of this Component.
  779        *
  780        * @exception LifecycleException if this component detects a fatal error
  781        *  that needs to be reported
  782        */
  783       public void stop() throws LifecycleException {
  784   
  785           // Perform normal superclass finalization
  786           super.stop();
  787   
  788           // Close any open DB connection
  789           close(this.dbConnection);
  790   
  791       }
  792   
  793   
  794   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]