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