View Javadoc

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