View Javadoc

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