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