View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.security;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.sql.Connection;
24  import java.sql.DriverManager;
25  import java.sql.PreparedStatement;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.Properties;
31  
32  import javax.servlet.ServletRequest;
33  
34  import org.eclipse.jetty.server.UserIdentity;
35  import org.eclipse.jetty.util.Loader;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  import org.eclipse.jetty.util.resource.Resource;
39  import org.eclipse.jetty.util.security.Credential;
40  
41  /* ------------------------------------------------------------ */
42  /**
43   * HashMapped User Realm with JDBC as data source. 
44   * The {@link #login(String, Object, ServletRequest)} method checks the inherited Map for the user. If the user is not
45   * found, it will fetch details from the database and populate the inherited
46   * Map. It then calls the superclass {@link #login(String, Object, ServletRequest)} method to perform the actual
47   * authentication. Periodically (controlled by configuration parameter),
48   * internal hashes are cleared. Caching can be disabled by setting cache refresh
49   * interval to zero. Uses one database connection that is initialized at
50   * startup. Reconnect on failures.
51   * <p> 
52   * An example properties file for configuration is in
53   * <code>${jetty.home}/etc/jdbcRealm.properties</code>
54   */
55  public class JDBCLoginService extends MappedLoginService
56  {
57      private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
58  
59      protected String _config;
60      protected String _jdbcDriver;
61      protected String _url;
62      protected String _userName;
63      protected String _password;
64      protected String _userTableKey;
65      protected String _userTablePasswordField;
66      protected String _roleTableRoleField;
67      protected int _cacheTime;
68      protected long _lastHashPurge;
69      protected Connection _con;
70      protected String _userSql;
71      protected String _roleSql;
72  
73      
74      /**
75       * JDBCKnownUser
76       */
77      public class JDBCKnownUser extends KnownUser
78      {
79          int _userKey;
80          
81          public JDBCKnownUser(String name, Credential credential, int key)
82          {
83              super(name, credential);
84              _userKey = key;
85          }
86          
87          
88          public int getUserKey ()
89          {
90              return _userKey;
91          }
92      }
93  
94      /* ------------------------------------------------------------ */
95      public JDBCLoginService()
96          throws IOException
97      {
98      }
99      
100     /* ------------------------------------------------------------ */
101     public JDBCLoginService(String name)
102         throws IOException
103     {
104         setName(name);
105     }
106     
107     /* ------------------------------------------------------------ */
108     public JDBCLoginService(String name, String config)
109         throws IOException
110     {
111         setName(name);
112         setConfig(config);
113     }
114     
115     /* ------------------------------------------------------------ */
116     public JDBCLoginService(String name, IdentityService identityService, String config)
117         throws IOException
118     {
119         setName(name);
120         setIdentityService(identityService);
121         setConfig(config);
122     }
123 
124 
125     /* ------------------------------------------------------------ */
126     /**
127      * @see org.eclipse.jetty.security.MappedLoginService#doStart()
128      */
129     @Override
130     protected void doStart() throws Exception
131     {
132         Properties properties = new Properties();
133         Resource resource = Resource.newResource(_config);
134         try (InputStream in = resource.getInputStream())
135         {
136             properties.load(in);
137         }
138         _jdbcDriver = properties.getProperty("jdbcdriver");
139         _url = properties.getProperty("url");
140         _userName = properties.getProperty("username");
141         _password = properties.getProperty("password");
142         String _userTable = properties.getProperty("usertable");
143         _userTableKey = properties.getProperty("usertablekey");
144         String _userTableUserField = properties.getProperty("usertableuserfield");
145         _userTablePasswordField = properties.getProperty("usertablepasswordfield");
146         String _roleTable = properties.getProperty("roletable");
147         String _roleTableKey = properties.getProperty("roletablekey");
148         _roleTableRoleField = properties.getProperty("roletablerolefield");
149         String _userRoleTable = properties.getProperty("userroletable");
150         String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
151         String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
152         _cacheTime = new Integer(properties.getProperty("cachetime"));
153 
154         if (_jdbcDriver == null || _jdbcDriver.equals("")
155             || _url == null
156             || _url.equals("")
157             || _userName == null
158             || _userName.equals("")
159             || _password == null
160             || _cacheTime < 0)
161         {
162             LOG.warn("UserRealm " + getName() + " has not been properly configured");
163         }
164         _cacheTime *= 1000;
165         _lastHashPurge = 0;
166         _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
167         _roleSql = "select r." + _roleTableRoleField
168                    + " from "
169                    + _roleTable
170                    + " r, "
171                    + _userRoleTable
172                    + " u where u."
173                    + _userRoleTableUserKey
174                    + " = ?"
175                    + " and r."
176                    + _roleTableKey
177                    + " = u."
178                    + _userRoleTableRoleKey;
179         
180         Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
181         super.doStart();
182     }
183 
184 
185     /* ------------------------------------------------------------ */
186     public String getConfig()
187     {
188         return _config;
189     }
190 
191     /* ------------------------------------------------------------ */
192     /**
193      * Load JDBC connection configuration from properties file.
194      * 
195      * @param config Filename or url of user properties file.
196      */
197     public void setConfig(String config)
198     {        
199         if (isRunning())
200             throw new IllegalStateException("Running");
201         _config=config;
202     }
203 
204     /* ------------------------------------------------------------ */
205     /**
206      * (re)Connect to database with parameters setup by loadConfig()
207      */
208     public void connectDatabase()
209     {
210         try
211         {
212             Class.forName(_jdbcDriver);
213             _con = DriverManager.getConnection(_url, _userName, _password);
214         }
215         catch (SQLException e)
216         {
217             LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
218         }
219         catch (ClassNotFoundException e)
220         {
221             LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
222         }
223     }
224 
225     /* ------------------------------------------------------------ */
226     @Override
227     public UserIdentity login(String username, Object credentials, ServletRequest request)
228     {
229         long now = System.currentTimeMillis();
230         if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
231         {
232             _users.clear();
233             _lastHashPurge = now;
234             closeConnection();
235         }
236         
237         return super.login(username,credentials, request);
238     }
239 
240     /* ------------------------------------------------------------ */
241     @Override
242     protected void loadUsers()
243     {   
244     }
245     
246     /* ------------------------------------------------------------ */
247     @Deprecated
248     protected UserIdentity loadUser(String username)
249     {
250         try
251         {
252             if (null == _con) 
253                 connectDatabase();
254 
255             if (null == _con) 
256                 throw new SQLException("Can't connect to database");
257 
258             try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
259             {
260                 stat1.setObject(1, username);
261                 try (ResultSet rs1 = stat1.executeQuery())
262                 {
263                     if (rs1.next())
264                     {
265                         int key = rs1.getInt(_userTableKey);
266                         String credentials = rs1.getString(_userTablePasswordField);
267 
268 
269                         List<String> roles = new ArrayList<String>();
270 
271                         try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
272                         {
273                             stat2.setInt(1, key);
274                             try (ResultSet rs2 = stat2.executeQuery())
275                             {
276                                 while (rs2.next())
277                                     roles.add(rs2.getString(_roleTableRoleField));
278                             }
279                         }
280                         return putUser(username, Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
281                     }
282                 }
283             }
284         }
285         catch (SQLException e)
286         {
287             LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
288             closeConnection();
289         }
290         return null;
291     }
292 
293 
294     /** 
295      * @see org.eclipse.jetty.security.MappedLoginService#loadUserInfo(java.lang.String)
296      */
297     public KnownUser loadUserInfo (String username)
298     {
299         try
300         {
301             if (null == _con) 
302                 connectDatabase();
303 
304             if (null == _con) 
305                 throw new SQLException("Can't connect to database");
306 
307             try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
308             {
309                 stat1.setObject(1, username);
310                 try (ResultSet rs1 = stat1.executeQuery())
311                 {
312                     if (rs1.next())
313                     {
314                         int key = rs1.getInt(_userTableKey);
315                         String credentials = rs1.getString(_userTablePasswordField);
316 
317                         return new JDBCKnownUser (username, Credential.getCredential(credentials), key);
318                     }
319                 }
320             }
321         }
322         catch (SQLException e)
323         {
324             LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
325             closeConnection();
326         }
327         
328         return null;
329     }
330 
331     
332     
333     /** 
334      * @see org.eclipse.jetty.security.MappedLoginService#loadRoleInfo(org.eclipse.jetty.security.MappedLoginService.KnownUser)
335      */
336     public String[] loadRoleInfo (KnownUser user)
337     {
338         JDBCKnownUser jdbcUser = (JDBCKnownUser)user;
339         
340         try
341         {
342             if (null == _con) 
343                 connectDatabase();
344 
345             if (null == _con) 
346                 throw new SQLException("Can't connect to database");
347             
348             
349             List<String> roles = new ArrayList<String>();
350 
351             try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
352             {
353                 stat2.setInt(1, jdbcUser.getUserKey());
354                 try (ResultSet rs2 = stat2.executeQuery())
355                 {
356                     while (rs2.next())
357                         roles.add(rs2.getString(_roleTableRoleField));
358                     return roles.toArray(new String[roles.size()]);
359                 }
360             }
361         }
362         catch (SQLException e)
363         {
364             LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
365             closeConnection();
366         }
367         
368         return null;
369     }
370     
371 
372     /**
373      * Close an existing connection
374      */
375     private void closeConnection ()
376     {
377         if (_con != null)
378         {
379             if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
380             try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
381         }
382         _con = null;
383     }
384 }