KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > enterprise > security > auth > realm > jdbc > JDBCRealm


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 package com.sun.enterprise.security.auth.realm.jdbc;
25
26 import java.io.UnsupportedEncodingException JavaDoc;
27 import java.security.MessageDigest JavaDoc;
28 import java.security.NoSuchAlgorithmException JavaDoc;
29 import java.sql.Connection JavaDoc;
30 import java.sql.PreparedStatement JavaDoc;
31 import java.sql.ResultSet JavaDoc;
32 import java.sql.SQLException JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.Enumeration JavaDoc;
35 import java.util.HashMap JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Map JavaDoc;
38 import java.util.Properties JavaDoc;
39 import java.util.Vector JavaDoc;
40 import java.util.logging.Logger JavaDoc;
41 import java.util.logging.Level JavaDoc;
42 import javax.sql.DataSource JavaDoc;
43
44 import sun.misc.BASE64Encoder;
45
46 import com.sun.enterprise.connectors.ConnectorRuntime;
47 import com.sun.enterprise.security.LoginException;
48 import com.sun.enterprise.security.auth.realm.IASRealm;
49 import com.sun.enterprise.security.auth.realm.BadRealmException;
50 import com.sun.enterprise.security.auth.realm.NoSuchUserException;
51 import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
52 import com.sun.enterprise.security.auth.realm.InvalidOperationException;
53
54
55 /**
56  * Realm for supporting JDBC authentication.
57  *
58  * <P>The JDBC realm needs the following properties in its configuration:
59  * <ul>
60  * <li>jaas-context : JAAS context name used to access LoginModule for
61  * authentication (for example JDBCRealm).
62  * <li>datasource-jndi : jndi name of datasource
63  * <li>db-user : user name to access the datasource
64  * <li>db-password : password to access the datasource
65  * <li>digest: digest mechanism
66  * <li>charset: charset encoding
67  * <li>user-table: table containing user name and password
68  * <li>group-table: table containing user name and group name
69  * <li>user-name-column: column corresponding to user name in user-table and group-table
70  * <li>password-column : column corresponding to password in user-table
71  * <li>group-name-column : column corresponding to group in group-table
72  * </ul>
73  *
74  * @see com.sun.enterprise.security.auth.login.SolarisLoginModule
75  *
76  */

77 public final class JDBCRealm extends IASRealm {
78     // Descriptive string of the authentication type of this realm.
79
public static final String JavaDoc AUTH_TYPE = "jdbc";
80
81     public static final String JavaDoc PARAM_DATASOURCE_JNDI = "datasource-jndi";
82     public static final String JavaDoc PARAM_DB_USER = "db-user";
83     public static final String JavaDoc PARAM_DB_PASSWORD = "db-password";
84
85     public static final String JavaDoc PARAM_DIGEST_ALGORITHM = "digest-algorithm";
86     public static final String JavaDoc DEFAULT_DIGEST_ALGORITHM = "MD5";
87     public static final String JavaDoc NONE = "none";
88
89     public static final String JavaDoc PARAM_ENCODING = "encoding";
90     public static final String JavaDoc HEX = "hex";
91     public static final String JavaDoc BASE64 = "base64";
92     public static final String JavaDoc DEFAULT_ENCODING = HEX; // for digest only
93

94     public static final String JavaDoc PARAM_CHARSET = "charset";
95     public static final String JavaDoc PARAM_USER_TABLE = "user-table";
96     public static final String JavaDoc PARAM_USER_NAME_COLUMN = "user-name-column";
97     public static final String JavaDoc PARAM_PASSWORD_COLUMN = "password-column";
98     public static final String JavaDoc PARAM_GROUP_TABLE = "group-table";
99     public static final String JavaDoc PARAM_GROUP_NAME_COLUMN = "group-name-column";
100
101     private static final char[] HEXADECIMAL = { '0', '1', '2', '3',
102         '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
103
104     private Map JavaDoc<String JavaDoc, Vector JavaDoc> groupCache;
105     private Vector JavaDoc<String JavaDoc> emptyVector;
106     private String JavaDoc passwordQuery = null;
107     private String JavaDoc groupQuery = null;
108     private MessageDigest JavaDoc md = null;
109     
110     /**
111      * Initialize a realm with some properties. This can be used
112      * when instantiating realms from their descriptions. This
113      * method may only be called a single time.
114      *
115      * @param props Initialization parameters used by this realm.
116      * @exception BadRealmException If the configuration parameters
117      * identify a corrupt realm.
118      * @exception NoSuchRealmException If the configuration parameters
119      * specify a realm which doesn't exist.
120      */

121     public synchronized void init(Properties JavaDoc props)
122             throws BadRealmException, NoSuchRealmException{
123         String JavaDoc jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
124         String JavaDoc dbUser = props.getProperty(PARAM_DB_USER);
125         String JavaDoc dbPassword = props.getProperty(PARAM_DB_PASSWORD);
126         String JavaDoc dsJndi = props.getProperty(PARAM_DATASOURCE_JNDI);
127         String JavaDoc digestAlgorithm = props.getProperty(PARAM_DIGEST_ALGORITHM,
128             DEFAULT_DIGEST_ALGORITHM);
129         String JavaDoc encoding = props.getProperty(PARAM_ENCODING);
130         String JavaDoc charset = props.getProperty(PARAM_CHARSET);
131         String JavaDoc userTable = props.getProperty(PARAM_USER_TABLE);
132         String JavaDoc userNameColumn = props.getProperty(PARAM_USER_NAME_COLUMN);
133         String JavaDoc passwordColumn = props.getProperty(PARAM_PASSWORD_COLUMN);
134         String JavaDoc groupTable = props.getProperty(PARAM_GROUP_TABLE);
135         String JavaDoc groupNameColumn = props.getProperty(PARAM_GROUP_NAME_COLUMN);
136         
137         if (jaasCtx == null) {
138             String JavaDoc msg = sm.getString(
139                 "realm.missingprop", IASRealm.JAAS_CONTEXT_PARAM, "JDBCRealm");
140             throw new BadRealmException(msg);
141         }
142
143         if (dsJndi == null) {
144             String JavaDoc msg = sm.getString(
145                 "realm.missingprop", PARAM_DATASOURCE_JNDI, "JDBCRealm");
146             throw new BadRealmException(msg);
147         }
148         if (userTable == null) {
149             String JavaDoc msg = sm.getString(
150                 "realm.missingprop", PARAM_USER_TABLE, "JDBCRealm");
151             throw new BadRealmException(msg);
152         }
153         if (groupTable == null) {
154             String JavaDoc msg = sm.getString(
155                 "realm.missingprop", PARAM_GROUP_TABLE, "JDBCRealm");
156             throw new BadRealmException(msg);
157         }
158         if (userNameColumn == null) {
159             String JavaDoc msg = sm.getString(
160                 "realm.missingprop", PARAM_USER_NAME_COLUMN, "JDBCRealm");
161             throw new BadRealmException(msg);
162         }
163         if (passwordColumn == null) {
164             String JavaDoc msg = sm.getString(
165                 "realm.missingprop", PARAM_PASSWORD_COLUMN, "JDBCRealm");
166             throw new BadRealmException(msg);
167         }
168         if (groupNameColumn == null) {
169             String JavaDoc msg = sm.getString(
170                 "realm.missingprop", PARAM_GROUP_NAME_COLUMN, "JDBCRealm");
171             throw new BadRealmException(msg);
172         }
173
174         passwordQuery = "SELECT " + passwordColumn + " FROM " + userTable +
175             " WHERE " + userNameColumn + " = ?";
176
177         groupQuery = "SELECT " + groupNameColumn + " FROM " + groupTable +
178             " WHERE " + userNameColumn + " = ? ";
179
180         if (!NONE.equalsIgnoreCase(digestAlgorithm)) {
181             try {
182                 md = MessageDigest.getInstance(digestAlgorithm);
183             } catch(NoSuchAlgorithmException JavaDoc e) {
184                 String JavaDoc msg = sm.getString("jdbcrealm.notsupportdigestalg",
185                     digestAlgorithm);
186                 throw new BadRealmException(msg);
187             }
188         }
189         if (md != null && encoding == null) {
190             encoding = DEFAULT_ENCODING;
191         }
192
193         this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);
194         if (dbUser != null && dbPassword != null) {
195             this.setProperty(PARAM_DB_USER, dbUser);
196             this.setProperty(PARAM_DB_PASSWORD, dbPassword);
197         }
198         this.setProperty(PARAM_DATASOURCE_JNDI, dsJndi);
199         this.setProperty(PARAM_DIGEST_ALGORITHM, digestAlgorithm);
200         if (encoding != null) {
201             this.setProperty(PARAM_ENCODING, encoding);
202         }
203         if (charset != null) {
204             this.setProperty(PARAM_CHARSET, charset);
205         }
206
207         if (_logger.isLoggable(Level.FINEST)) {
208             _logger.finest("JDBCRealm : " +
209                 IASRealm.JAAS_CONTEXT_PARAM + "= " + jaasCtx + ", " +
210                 PARAM_DATASOURCE_JNDI + " = " + dsJndi + ", " +
211                 PARAM_DB_USER + " = " + dbUser + ", " +
212                 PARAM_DIGEST_ALGORITHM + " = " + digestAlgorithm + ", " +
213                 PARAM_ENCODING + " = " + encoding + ", " +
214                 PARAM_CHARSET + " = " + charset);
215         }
216
217         groupCache = new HashMap JavaDoc<String JavaDoc, Vector JavaDoc>();
218         emptyVector = new Vector JavaDoc<String JavaDoc>();
219     }
220
221     /**
222      * Returns a short (preferably less than fifteen characters) description
223      * of the kind of authentication which is supported by this realm.
224      *
225      * @return Description of the kind of authentication that is directly
226      * supported by this realm.
227      */

228     public String JavaDoc getAuthType(){
229         return AUTH_TYPE;
230     }
231
232     /**
233      * Returns the name of all the groups that this user belongs to.
234      * It loads the result from groupCache first.
235      * This is called from web path group verification, though
236      * it should not be.
237      *
238      * @param username Name of the user in this realm whose group listing
239      * is needed.
240      * @return Enumeration of group names (strings).
241      * @exception InvalidOperationException thrown if the realm does not
242      * support this operation - e.g. Certificate realm does not support
243      * this operation.
244      */

245     public Enumeration JavaDoc getGroupNames(String JavaDoc username)
246             throws InvalidOperationException, NoSuchUserException {
247         Vector JavaDoc vector = groupCache.get(username);
248         if (vector == null) {
249             String JavaDoc[] grps = findGroups(username);
250             setGroupNames(username, grps);
251             vector = groupCache.get(username);
252         }
253         return vector.elements();
254     }
255
256     private void setGroupNames(String JavaDoc username, String JavaDoc[] groups) {
257         Vector JavaDoc<String JavaDoc> v = null;
258         
259         if (groups == null) {
260             v = emptyVector;
261
262         } else {
263             v = new Vector JavaDoc<String JavaDoc>(groups.length + 1);
264             for (int i=0; i<groups.length; i++) {
265                 v.add(groups[i]);
266             }
267         }
268         
269         synchronized (this) {
270             groupCache.put(username, v);
271         }
272     }
273
274
275     /**
276      * Invoke the native authentication call.
277      *
278      * @param username User to authenticate.
279      * @param password Given password.
280      * @returns true of false, indicating authentication status.
281      *
282      */

283     public String JavaDoc[] authenticate(String JavaDoc username, String JavaDoc password) {
284         String JavaDoc[] groups = null;
285         if (isUserValid(username, password)) {
286             groups = findGroups(username);
287             setGroupNames(username, groups);
288         }
289         return groups;
290     }
291
292     /**
293      * Test if a user is valid
294      * @param user user's identifier
295      * @param password user's password
296      * @return true if valid
297      */

298     private boolean isUserValid(String JavaDoc user, String JavaDoc password) {
299         Connection JavaDoc connection = null;
300         PreparedStatement JavaDoc statement = null;
301         ResultSet JavaDoc rs = null;
302         boolean valid = false;
303
304         try {
305             String JavaDoc hpwd = hashPassword(password);
306             connection = getConnection();
307             statement = connection.prepareStatement(passwordQuery);
308             statement.setString(1,user);
309             rs = statement.executeQuery();
310             String JavaDoc pwd = null;
311             if (rs.next()) {
312                 pwd = rs.getString(1);
313                 if (HEX.equalsIgnoreCase(getProperty(PARAM_ENCODING))) {
314                     valid = pwd.equalsIgnoreCase(hpwd);
315                 } else {
316                     valid = pwd.equals(hpwd);
317                 }
318             }
319         } catch(Exception JavaDoc ex) {
320             _logger.log(Level.SEVERE, "jdbcrealm.invaliduser", user);
321             if (_logger.isLoggable(Level.FINE)) {
322                 _logger.log(Level.FINE, "Cannot validate user", ex);
323             }
324         } finally {
325             close(connection, statement, rs);
326         }
327         return valid;
328     }
329
330     private String JavaDoc hashPassword(String JavaDoc password)
331             throws UnsupportedEncodingException JavaDoc{
332         String JavaDoc result = null;
333         byte[] bytes = null;
334         String JavaDoc charSet = getProperty(PARAM_CHARSET);
335         if (charSet != null) {
336             bytes = password.getBytes(charSet);
337         } else {
338             bytes = password.getBytes();
339         }
340         if (md != null) {
341             synchronized(md) {
342                 md.reset();
343                 bytes = md.digest(bytes);
344             }
345         }
346
347         String JavaDoc encoding = getProperty(PARAM_ENCODING);
348         if (HEX.equalsIgnoreCase(encoding)) {
349             result = hexEncode(bytes);
350         } else if (BASE64.equalsIgnoreCase(encoding)) {
351             result = base64Encode(bytes);
352         } else { // no encoding specified
353
result = new String JavaDoc(bytes);
354         }
355         return result;
356     }
357
358     private String JavaDoc hexEncode(byte[] bytes) {
359         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(2 * bytes.length);
360         for (int i = 0; i < bytes.length; i++) {
361             int low = (int)(bytes[i] & 0x0f);
362             int high = (int)((bytes[i] & 0xf0) >> 4);
363             sb.append(HEXADECIMAL[high]);
364             sb.append(HEXADECIMAL[low]);
365         }
366         return sb.toString();
367     }
368
369     private String JavaDoc base64Encode(byte[] bytes) {
370         BASE64Encoder encoder = new BASE64Encoder();
371         return encoder.encode(bytes);
372     }
373
374     /**
375      * Delegate method for retreiving users groups
376      * @param user user's identifier
377      * @return array of group key
378      */

379     private String JavaDoc[] findGroups(String JavaDoc user) {
380         Connection JavaDoc connection = null;
381         PreparedStatement JavaDoc statement = null;
382         ResultSet JavaDoc rs = null;
383         try{
384             connection = getConnection();
385             statement = connection.prepareStatement(groupQuery);
386             statement.setString(1,user);
387             rs = statement.executeQuery();
388             final List JavaDoc<String JavaDoc> groups = new ArrayList JavaDoc<String JavaDoc>();
389             while (rs.next()) {
390                 groups.add(rs.getString(1));
391             }
392             final String JavaDoc[] groupArray = new String JavaDoc[groups.size()];
393             return groups.toArray(groupArray);
394         } catch(Exception JavaDoc ex) {
395             _logger.log(Level.SEVERE, "jdbcrealm.grouperror", user);
396             if (_logger.isLoggable(Level.FINE)) {
397                 _logger.log(Level.FINE, "Cannot load group", ex);
398             }
399             return null;
400         } finally {
401             close(connection, statement, rs);
402         }
403     }
404
405     private void close(Connection JavaDoc conn, PreparedStatement JavaDoc stmt,
406             ResultSet JavaDoc rs) {
407         if (rs != null) {
408             try {
409                 rs.close();
410             } catch(Exception JavaDoc ex) {
411             }
412         }
413             
414         if (stmt != null) {
415             try {
416                 stmt.close();
417             } catch(Exception JavaDoc ex) {
418             }
419         }
420             
421         if (conn != null) {
422             try {
423                 conn.close();
424             } catch(Exception JavaDoc ex) {
425             }
426         }
427     }
428
429     /**
430      * Return a connection from the properties configured
431      * @return a connection
432      */

433     private Connection JavaDoc getConnection() throws LoginException {
434
435         final String JavaDoc dsJndi = this.getProperty(PARAM_DATASOURCE_JNDI);
436         final String JavaDoc dbUser = this.getProperty(PARAM_DB_USER);
437         final String JavaDoc dbPassword = this.getProperty(PARAM_DB_PASSWORD);
438         try{
439             final DataSource JavaDoc dataSource =
440                 (DataSource JavaDoc)ConnectorRuntime.getRuntime().lookupNonTxResource(dsJndi);
441             
442             Connection JavaDoc connection = null;
443             if (dbUser != null && dbPassword != null) {
444                 connection = dataSource.getConnection(dbUser, dbPassword);
445             } else {
446                 connection = dataSource.getConnection();
447             }
448             return connection;
449         } catch(Exception JavaDoc ex) {
450             String JavaDoc msg = sm.getString("jdbcrealm.cantconnect", dsJndi, dbUser);
451             LoginException loginEx = new LoginException(msg);
452             loginEx.initCause(ex);
453             throw loginEx;
454         }
455     }
456 }
457
Popular Tags