View Javadoc

    1   // ========================================================================
    2   // $Id: JDBCUserRealm.java 5120 2009-05-16 04:22:27Z janb $
    3   // Copyright 2003-2004 Mort Bay Consulting Pty. Ltd.
    4   // ------------------------------------------------------------------------
    5   // Licensed under the Apache License, Version 2.0 (the "License");
    6   // you may not use this file except in compliance with the License.
    7   // You may obtain a copy of the License at
    8   // http://www.apache.org/licenses/LICENSE-2.0
    9   // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  // ========================================================================
    15
    16  package org.mortbay.jetty.security;
    17
    18  import java.io.IOException;
    19  import java.security.Principal;
    20  import java.sql.Connection;
    21  import java.sql.DriverManager;
    22  import java.sql.PreparedStatement;
    23  import java.sql.ResultSet;
    24  import java.sql.SQLException;
    25  import java.util.Properties;
    26
    27  import org.mortbay.jetty.Request;
    28  import org.mortbay.log.Log;
    29  import org.mortbay.resource.Resource;
    30  import org.mortbay.util.Loader;
    31
    32  /* ------------------------------------------------------------ */
    33  /** HashMapped User Realm with JDBC as data source.
    34   * JDBCUserRealm extends HashUserRealm and adds a method to fetch user
    35   * information from database.
    36   * The authenticate() method checks the inherited HashMap for the user.
    37   * If the user is not found, it will fetch details from the database
    38   * and populate the inherited HashMap. It then calls the HashUserRealm
    39   * authenticate() method to perform the actual authentication.
    40   * Periodically (controlled by configuration parameter), internal
    41   * hashes are cleared. Caching can be disabled by setting cache
    42   * refresh interval to zero.
    43   * Uses one database connection that is initialized at startup. Reconnect
    44   * on failures. authenticate() is 'synchronized'.
    45   *
    46   * An example properties file for configuration is in
    47   * $JETTY_HOME/etc/jdbcRealm.properties
    48   *
    49   * @version $Id: JDBCUserRealm.java 5120 2009-05-16 04:22:27Z janb $
    50   * @author Arkadi Shishlov (arkadi)
    51   * @author Fredrik Borgh
    52   * @author Greg Wilkins (gregw)
    53   * @author Ben Alex
    54   */
    55
    56  public class JDBCUserRealm extends HashUserRealm implements UserRealm
    57  {
    58
    59      private String _jdbcDriver;
    60      private String _url;
    61      private String _userName;
    62      private String _password;
    63      private String _userTable;
    64      private String _userTableKey;
    65      private String _userTableUserField;
    66      private String _userTablePasswordField;
    67      private String _roleTable;
    68      private String _roleTableKey;
    69      private String _roleTableRoleField;
    70      private String _userRoleTable;
    71      private String _userRoleTableUserKey;
    72      private String _userRoleTableRoleKey;
    73      private int _cacheTime;
    74
    75      private long _lastHashPurge;
    76      private Connection _con;
    77      private String _userSql;
    78      private String _roleSql;
    79
    80      /* ------------------------------------------------------------ */
    81      /** Constructor.
    82       */
    83      public JDBCUserRealm()
    84      {
    85          super();
    86      }
    87
    88      /* ------------------------------------------------------------ */
    89      /** Constructor.
    90       * @param name
    91       */
    92      public JDBCUserRealm(String name)
    93      {
    94          super(name);
    95      }
    96
    97      /* ------------------------------------------------------------ */
    98      /** Constructor.
    99       * @param name Realm name
    100      * @param config Filename or url of JDBC connection properties file.
    101      * @exception IOException
    102      * @exception ClassNotFoundException
    103      */
    104     public JDBCUserRealm(String name, String config)
    105         throws IOException,
    106                ClassNotFoundException,
    107                InstantiationException,
    108                IllegalAccessException
    109     {
    110         super(name);
    111         setConfig(config);
    112         Loader.loadClass(this.getClass(),_jdbcDriver).newInstance();
    113     }
    114
    115     /* ------------------------------------------------------------ */
    116     /** Load JDBC connection configuration from properties file.
    117      *
    118      * @exception IOException
    119      */
    120     protected void loadConfig()
    121         throws IOException
    122     {
    123         Properties properties = new Properties();
    124
    125         properties.load(getConfigResource().getInputStream());
    126
    127         _jdbcDriver = properties.getProperty("jdbcdriver");
    128         _url = properties.getProperty("url");
    129         _userName = properties.getProperty("username");
    130         _password = properties.getProperty("password");
    131         _userTable = properties.getProperty("usertable");
    132         _userTableKey = properties.getProperty("usertablekey");
    133         _userTableUserField = properties.getProperty("usertableuserfield");
    134         _userTablePasswordField = properties.getProperty("usertablepasswordfield");
    135         _roleTable = properties.getProperty("roletable");
    136         _roleTableKey = properties.getProperty("roletablekey");
    137         _roleTableRoleField = properties.getProperty("roletablerolefield");
    138         _userRoleTable = properties.getProperty("userroletable");
    139         _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
    140         _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
    141         // default cachetime = 30s
    142         String cachetime = properties.getProperty("cachetime");
    143         _cacheTime = cachetime!=null ? new Integer(cachetime).intValue() : 30;
    144
    145         if (_jdbcDriver == null || _jdbcDriver.equals("")
    146             || _url == null || _url.equals("")
    147             || _userName == null || _userName.equals("")
    148             || _password == null
    149             || _cacheTime < 0)
    150         {
    151             if(Log.isDebugEnabled())Log.debug("UserRealm " + getName()
    152                         + " has not been properly configured");
    153         }
    154         _cacheTime *= 1000;
    155         _lastHashPurge = 0;
    156         _userSql = "select " + _userTableKey + ","
    157             + _userTablePasswordField + " from "
    158             + _userTable + " where "
    159             + _userTableUserField + " = ?";
    160         _roleSql = "select r." + _roleTableRoleField
    161             + " from " + _roleTable + " r, "
    162             + _userRoleTable + " u where u."
    163             + _userRoleTableUserKey + " = ?"
    164             + " and r." + _roleTableKey + " = u."
    165             + _userRoleTableRoleKey;
    166     }
    167
    168     /* ------------------------------------------------------------ */
    169     public void logout(Principal user)
    170     {}
    171
    172     /* ------------------------------------------------------------ */
    173     /** (re)Connect to database with parameters setup by loadConfig()
    174      */
    175     public void connectDatabase()
    176     {
    177         try
    178         {
    179              Class.forName(_jdbcDriver);
    180             _con = DriverManager.getConnection(_url, _userName, _password);
    181         }
    182         catch(SQLException e)
    183         {
    184             Log.warn("UserRealm " + getName()
    185                       + " could not connect to database; will try later", e);
    186         }
    187         catch(ClassNotFoundException e)
    188         {
    189             Log.warn("UserRealm " + getName()
    190                       + " could not connect to database; will try later", e);
    191         }
    192     }
    193
    194     /* ------------------------------------------------------------ */
    195     public Principal authenticate(String username,
    196                                   Object credentials,
    197                                   Request request)
    198     {
    199         synchronized (this)
    200         {
    201             long now = System.currentTimeMillis();
    202             if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
    203             {
    204                 _users.clear();
    205                 _roles.clear();
    206                 _lastHashPurge = now;
    207                 closeConnection(); //force a fresh connection
    208             }
    209             Principal user = super.getPrincipal(username);
    210             if (user == null)
    211             {
    212                 loadUser(username);
    213                 user = super.getPrincipal(username);
    214             }
    215         }
    216         return super.authenticate(username, credentials, request);
    217     }
    218
    219     /* ------------------------------------------------------------ */
    220     /** Check if a user is in a role.
    221      * @param user The user, which must be from this realm
    222      * @param roleName
    223      * @return True if the user can act in the role.
    224      */
    225     public synchronized boolean isUserInRole(Principal user, String roleName)
    226     {
    227         if(super.getPrincipal(user.getName())==null)
    228             loadUser(user.getName());
    229         return super.isUserInRole(user, roleName);
    230     }
    231
    232
    233
    234
    235     /* ------------------------------------------------------------ */
    236     private void loadUser(String username)
    237     {
    238         try
    239         {
    240             if (null==_con)
    241                 connectDatabase();
    242
    243             if (null==_con)
    244                 throw new SQLException("Can't connect to database");
    245
    246             PreparedStatement stat = _con.prepareStatement(_userSql);
    247             stat.setObject(1, username);
    248             ResultSet rs = stat.executeQuery();
    249
    250             if (rs.next())
    251             {
    252                 int key = rs.getInt(_userTableKey);
    253                 put(username, rs.getString(_userTablePasswordField));
    254                 stat.close();
    255
    256                 stat = _con.prepareStatement(_roleSql);
    257                 stat.setInt(1, key);
    258                 rs = stat.executeQuery();
    259
    260                 while (rs.next())
    261                     addUserToRole(username, rs.getString(_roleTableRoleField));
    262
    263                 stat.close();
    264             }
    265         }
    266         catch (SQLException e)
    267         {
    268             Log.warn("UserRealm " + getName()
    269                       + " could not load user information from database", e);
    270            closeConnection();
    271         }
    272     }
    273
    274     /**
    275      * Close an existing connection
    276      */
    277     private void closeConnection ()
    278     {
    279         if (_con != null)
    280         {
    281             if (Log.isDebugEnabled()) Log.debug("Closing db connection for JDBCUserRealm");
    282             try { _con.close(); }catch (Exception e) {Log.ignore(e);}
    283         }
    284         _con = null;
    285     }
    286 }