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