View Javadoc

1   // ========================================================================
2   // Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.security;
15  
16  import java.io.IOException;
17  import java.sql.Connection;
18  import java.sql.DriverManager;
19  import java.sql.PreparedStatement;
20  import java.sql.ResultSet;
21  import java.sql.SQLException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Properties;
25  
26  import org.eclipse.jetty.http.security.Password;
27  import org.eclipse.jetty.server.UserIdentity;
28  import org.eclipse.jetty.util.Loader;
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.resource.Resource;
31  
32  /* ------------------------------------------------------------ */
33  /**
34   * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
35   * HashULoginService and adds a method to fetch user information from database.
36   * The login() method checks the inherited Map for the user. If the user is not
37   * found, it will fetch details from the database and populate the inherited
38   * Map. It then calls the superclass login() method to perform the actual
39   * authentication. Periodically (controlled by configuration parameter),
40   * internal hashes are cleared. Caching can be disabled by setting cache refresh
41   * interval to zero. Uses one database connection that is initialized at
42   * startup. Reconnect on failures. authenticate() is 'synchronized'.
43   * 
44   * An example properties file for configuration is in
45   * $JETTY_HOME/etc/jdbcRealm.properties
46   * 
47   * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
48   * 
49   * 
50   * 
51   * 
52   */
53  
54  public class JDBCLoginService extends MappedLoginService
55  {
56      private String _config;
57      private String _jdbcDriver;
58      private String _url;
59      private String _userName;
60      private String _password;
61      private String _userTableKey;
62      private String _userTablePasswordField;
63      private String _roleTableRoleField;
64      private int _cacheTime;
65      private long _lastHashPurge;
66      private Connection _con;
67      private String _userSql;
68      private String _roleSql;
69  
70  
71      /* ------------------------------------------------------------ */
72      public JDBCLoginService()
73          throws IOException
74      {
75      }
76      
77      /* ------------------------------------------------------------ */
78      public JDBCLoginService(String name)
79          throws IOException
80      {
81          setName(name);
82      }
83      
84      /* ------------------------------------------------------------ */
85      public JDBCLoginService(String name, String config)
86          throws IOException
87      {
88          setName(name);
89          setConfig(config);
90      }
91      
92      /* ------------------------------------------------------------ */
93      public JDBCLoginService(String name, IdentityService identityService, String config)
94          throws IOException
95      {
96          setName(name);
97          setIdentityService(identityService);
98          setConfig(config);
99      }
100 
101 
102     /* ------------------------------------------------------------ */
103     /**
104      * @see org.eclipse.jetty.security.MappedLoginService#doStart()
105      */
106     @Override
107     protected void doStart() throws Exception
108     {
109         Properties properties = new Properties();
110         Resource resource = Resource.newResource(_config);
111         properties.load(resource.getInputStream());
112 
113         _jdbcDriver = properties.getProperty("jdbcdriver");
114         _url = properties.getProperty("url");
115         _userName = properties.getProperty("username");
116         _password = properties.getProperty("password");
117         String _userTable = properties.getProperty("usertable");
118         _userTableKey = properties.getProperty("usertablekey");
119         String _userTableUserField = properties.getProperty("usertableuserfield");
120         _userTablePasswordField = properties.getProperty("usertablepasswordfield");
121         String _roleTable = properties.getProperty("roletable");
122         String _roleTableKey = properties.getProperty("roletablekey");
123         _roleTableRoleField = properties.getProperty("roletablerolefield");
124         String _userRoleTable = properties.getProperty("userroletable");
125         String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
126         String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
127         _cacheTime = new Integer(properties.getProperty("cachetime"));
128 
129         if (_jdbcDriver == null || _jdbcDriver.equals("")
130             || _url == null
131             || _url.equals("")
132             || _userName == null
133             || _userName.equals("")
134             || _password == null
135             || _cacheTime < 0)
136         {
137             if (Log.isDebugEnabled()) Log.debug("UserRealm " + getName() + " has not been properly configured");
138         }
139         _cacheTime *= 1000;
140         _lastHashPurge = 0;
141         _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
142         _roleSql = "select r." + _roleTableRoleField
143                    + " from "
144                    + _roleTable
145                    + " r, "
146                    + _userRoleTable
147                    + " u where u."
148                    + _userRoleTableUserKey
149                    + " = ?"
150                    + " and r."
151                    + _roleTableKey
152                    + " = u."
153                    + _userRoleTableRoleKey;
154         
155         Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
156         super.doStart();
157     }
158 
159 
160     /* ------------------------------------------------------------ */
161     public String getConfig()
162     {
163         return _config;
164     }
165 
166     /* ------------------------------------------------------------ */
167     /**
168      * Load JDBC connection configuration from properties file.
169      * 
170      * @param config Filename or url of user properties file.
171      * @exception java.io.IOException
172      */
173     public void setConfig(String config)
174     {        
175         if (isRunning())
176             throw new IllegalStateException("Running");
177         _config=config;
178     }
179 
180     /* ------------------------------------------------------------ */
181     /**
182      * (re)Connect to database with parameters setup by loadConfig()
183      */
184     public void connectDatabase()
185     {
186         try
187         {
188             Class.forName(_jdbcDriver);
189             _con = DriverManager.getConnection(_url, _userName, _password);
190         }
191         catch (SQLException e)
192         {
193             Log.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
194         }
195         catch (ClassNotFoundException e)
196         {
197             Log.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
198         }
199     }
200 
201     /* ------------------------------------------------------------ */
202     @Override
203     public UserIdentity login(String username, Object credentials)
204     {
205         long now = System.currentTimeMillis();
206         if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
207         {
208             _users.clear();
209             _lastHashPurge = now;
210             closeConnection();
211         }
212         
213         return super.login(username,credentials);
214     }
215 
216 
217     /* ------------------------------------------------------------ */
218     @Override
219     protected void loadUsers()
220     {   
221     }
222     
223     /* ------------------------------------------------------------ */
224     @Override
225     protected UserIdentity loadUser(String username)
226     {
227         try
228         {
229             if (null == _con) 
230                 connectDatabase();
231 
232             if (null == _con) 
233                 throw new SQLException("Can't connect to database");
234 
235             PreparedStatement stat = _con.prepareStatement(_userSql);
236             stat.setObject(1, username);
237             ResultSet rs = stat.executeQuery();
238 
239             if (rs.next())
240             {
241                 int key = rs.getInt(_userTableKey);
242                 String credentials = rs.getString(_userTablePasswordField);
243                 stat.close();
244 
245                 stat = _con.prepareStatement(_roleSql);
246                 stat.setInt(1, key);
247                 rs = stat.executeQuery();
248                 List<String> roles = new ArrayList<String>();
249                 while (rs.next())
250                     roles.add(rs.getString(_roleTableRoleField));
251 
252                 stat.close();
253                 return putUser(username, new Password(credentials),roles.toArray(new String[roles.size()]));
254             }
255         }
256         catch (SQLException e)
257         {
258             Log.warn("UserRealm " + getName() + " could not load user information from database", e);
259             closeConnection();
260         }
261         return null;
262     }
263 
264     /**
265      * Close an existing connection
266      */
267     private void closeConnection ()
268     {
269         if (_con != null)
270         {
271             if (Log.isDebugEnabled()) Log.debug("Closing db connection for JDBCUserRealm");
272             try { _con.close(); }catch (Exception e) {Log.ignore(e);}
273         }
274         _con = null;
275     }
276 
277 }