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