KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > geronimo > security > jaas > server > JaasLoginService


1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.geronimo.security.jaas.server;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.apache.geronimo.common.GeronimoSecurityException;
23 import org.apache.geronimo.gbean.GBeanInfo;
24 import org.apache.geronimo.gbean.GBeanInfoBuilder;
25 import org.apache.geronimo.gbean.GBeanLifecycle;
26 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
27 import org.apache.geronimo.security.ContextManager;
28 import org.apache.geronimo.security.IdentificationPrincipal;
29 import org.apache.geronimo.security.SubjectId;
30 import org.apache.geronimo.security.jaas.LoginUtils;
31 import org.apache.geronimo.security.realm.SecurityRealm;
32
33 import javax.crypto.Mac;
34 import javax.crypto.SecretKey;
35 import javax.crypto.spec.SecretKeySpec;
36
37 import javax.security.auth.Subject JavaDoc;
38 import javax.security.auth.callback.Callback JavaDoc;
39 import javax.security.auth.login.LoginException JavaDoc;
40 import javax.security.auth.spi.LoginModule JavaDoc;
41 import java.security.InvalidKeyException JavaDoc;
42 import java.security.NoSuchAlgorithmException JavaDoc;
43 import java.security.Principal JavaDoc;
44
45 import java.util.Collection JavaDoc;
46 import java.util.HashMap JavaDoc;
47 import java.util.Hashtable JavaDoc;
48 import java.util.Iterator JavaDoc;
49 import java.util.LinkedList JavaDoc;
50 import java.util.List JavaDoc;
51 import java.util.Map JavaDoc;
52 import java.util.Set JavaDoc;
53 import java.util.Timer JavaDoc;
54 import java.util.TimerTask JavaDoc;
55
56 /**
57  * The single point of contact for Geronimo JAAS realms. Instead of attempting
58  * to interact with JAAS realms directly, a client should either interact with
59  * this service, or use a LoginModule implementation that interacts with this
60  * service.
61  *
62  * @version $Rev: 486195 $ $Date: 2006-12-12 10:42:02 -0500 (Tue, 12 Dec 2006) $
63  */

64 public class JaasLoginService implements GBeanLifecycle, JaasLoginServiceMBean {
65     public static final Log log = LogFactory.getLog(JaasLoginService.class);
66     private final static int DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL = 300000; // 5 mins
67
private final static int DEFAULT_MAX_LOGIN_DURATION = 1000 * 3600 * 24; // 1 day
68
private final static Timer JavaDoc clockDaemon = new Timer JavaDoc(/* Name requires JDK 1.5 "LoginService login modules monitor", */ true);
69     private static long nextLoginModuleId = System.currentTimeMillis();
70     private Collection JavaDoc realms;
71     private final String JavaDoc objectName;
72     private final SecretKey key;
73     private final String JavaDoc algorithm;
74     private final ClassLoader JavaDoc classLoader;
75     private final Map JavaDoc activeLogins = new Hashtable JavaDoc();
76     private int expiredLoginScanIntervalMillis = DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL;
77     private int maxLoginDurationMillis = DEFAULT_MAX_LOGIN_DURATION;
78     private ExpirationMonitor expirationMonitor;
79
80     public JaasLoginService(String JavaDoc algorithm, String JavaDoc password, ClassLoader JavaDoc classLoader, String JavaDoc objectName) {
81         this.classLoader = classLoader;
82         this.algorithm = algorithm;
83         key = new SecretKeySpec(password.getBytes(), algorithm);
84         this.objectName = objectName;
85     }
86
87     public String JavaDoc getObjectName() {
88         return objectName;
89     }
90
91     /**
92      * GBean property
93      */

94     public Collection JavaDoc getRealms() {
95         return realms;
96     }
97
98     /**
99      * GBean property
100      */

101     public void setRealms(Collection JavaDoc realms) {
102         this.realms = realms;
103         //todo: add listener to drop logins when realm is removed
104
}
105
106     /**
107      * GBean property
108      */

109     public int getMaxLoginDurationMillis() {
110         return maxLoginDurationMillis;
111     }
112
113     /**
114      * GBean property
115      */

116     public void setMaxLoginDurationMillis(int maxLoginDurationMillis) {
117         if (maxLoginDurationMillis == 0) {
118             maxLoginDurationMillis = DEFAULT_MAX_LOGIN_DURATION;
119         }
120         this.maxLoginDurationMillis = maxLoginDurationMillis;
121     }
122
123     /**
124      * GBean property
125      */

126     public int getExpiredLoginScanIntervalMillis() {
127         return expiredLoginScanIntervalMillis;
128     }
129
130     /**
131      * GBean property
132      */

133     public void setExpiredLoginScanIntervalMillis(int expiredLoginScanIntervalMillis) {
134         if (expiredLoginScanIntervalMillis == 0) {
135             expiredLoginScanIntervalMillis = DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL;
136         }
137         this.expiredLoginScanIntervalMillis = expiredLoginScanIntervalMillis;
138     }
139
140     public void doStart() throws Exception JavaDoc {
141         expirationMonitor = new ExpirationMonitor();
142
143         clockDaemon.scheduleAtFixedRate(
144                 expirationMonitor, expiredLoginScanIntervalMillis, expiredLoginScanIntervalMillis);
145     }
146
147     public void doStop() throws Exception JavaDoc {
148         if (expirationMonitor != null) {
149             expirationMonitor.cancel();
150             expirationMonitor = null;
151         }
152
153         //todo: shut down all logins
154
}
155
156     public void doFail() {
157         //todo: shut down all logins
158
}
159
160     /**
161      * Starts a new authentication process on behalf of an end user. The
162      * returned ID will identify that user throughout the user's interaction
163      * with the server. On the server side, that means maintaining the
164      * Subject and Principals for the user.
165      *
166      * @return The client handle used as an argument for the rest of the
167      * methods in this class.
168      */

169     public JaasSessionId connectToRealm(String JavaDoc realmName) {
170         SecurityRealm realm;
171         realm = getRealm(realmName);
172         if (realm == null) {
173             throw new GeronimoSecurityException("No such realm (" + realmName + ")");
174         } else {
175             return initializeClient(realm);
176         }
177     }
178
179     /**
180      * Gets the login module configuration for the specified realm. The
181      * caller needs that in order to perform the authentication process.
182      */

183     public JaasLoginModuleConfiguration[] getLoginConfiguration(JaasSessionId sessionHandle) throws LoginException JavaDoc {
184         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
185         if (session == null) {
186             throw new ExpiredLoginModuleException();
187         }
188         JaasLoginModuleConfiguration[] config = session.getModules();
189         // strip out non-serializable configuration options
190
JaasLoginModuleConfiguration[] result = new JaasLoginModuleConfiguration[config.length];
191         for (int i = 0; i < config.length; i++) {
192             result[i] = LoginUtils.getSerializableCopy(config[i]);
193         }
194         return result;
195     }
196
197     /**
198      * Retrieves callbacks for a server side login module. When the client
199      * is going through the configured login modules, if a specific login
200      * module is client-side, it will be handled directly. If it is
201      * server-side, the client gets the callbacks (using this method),
202      * populates them, and sends them back to the server.
203      */

204     public Callback JavaDoc[] getServerLoginCallbacks(JaasSessionId sessionHandle, int loginModuleIndex) throws LoginException JavaDoc {
205         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
206         checkContext(session, loginModuleIndex);
207         LoginModule JavaDoc module = session.getLoginModule(loginModuleIndex);
208
209         session.getHandler().setExploring();
210         try {
211             module.initialize(session.getSubject(), session.getHandler(), new HashMap JavaDoc(), session.getOptions(loginModuleIndex));
212         } catch (Exception JavaDoc e) {
213             log.error("Failed to initialize module", e);
214         }
215         try {
216             module.login();
217         } catch (LoginException JavaDoc e) {
218             //expected
219
}
220         try {
221             module.abort();
222         } catch (LoginException JavaDoc e) {
223             //makes no difference
224
}
225         return session.getHandler().finalizeCallbackList();
226     }
227
228     /**
229      * Returns populated callbacks for a server side login module. When the
230      * client is going through the configured login modules, if a specific
231      * login module is client-side, it will be handled directly. If it is
232      * server-side, the client gets the callbacks, populates them, and sends
233      * them back to the server (using this method).
234      */

235     public boolean performLogin(JaasSessionId sessionHandle, int loginModuleIndex, Callback JavaDoc[] results) throws LoginException JavaDoc {
236         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
237         checkContext(session, loginModuleIndex);
238         try {
239             session.getHandler().setClientResponse(results);
240         } catch (IllegalArgumentException JavaDoc iae) {
241             throw new LoginException JavaDoc(iae.toString());
242         }
243         return session.getLoginModule(loginModuleIndex).login();
244     }
245
246     /**
247      * Indicates that the overall login succeeded, and some principals were
248      * generated by a client-side login module. This method needs to be called
249      * once for each client-side login module, to specify Principals for each
250      * module.
251      */

252     public boolean performCommit(JaasSessionId sessionHandle, int loginModuleIndex) throws LoginException JavaDoc {
253         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
254         checkContext(session, loginModuleIndex);
255         return session.getLoginModule(loginModuleIndex).commit();
256     }
257
258     /**
259      * Indicates that the overall login failed. This method needs to be called
260      * once for each client-side login module.
261      */

262     public boolean performAbort(JaasSessionId sessionHandle, int loginModuleIndex) throws LoginException JavaDoc {
263         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
264         checkContext(session, loginModuleIndex);
265         return session.getLoginModule(loginModuleIndex).abort();
266     }
267
268     /**
269      * Indicates that the overall login succeeded. All login modules that were
270      * touched should have been logged in and committed before calling this.
271      */

272     public Principal loginSucceeded(JaasSessionId sessionHandle) throws LoginException JavaDoc {
273         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
274         if (session == null) {
275             throw new ExpiredLoginModuleException();
276         }
277
278         Subject JavaDoc subject = session.getSubject();
279         ContextManager.registerSubject(subject);
280         SubjectId id = ContextManager.getSubjectId(subject);
281         IdentificationPrincipal principal = new IdentificationPrincipal(id);
282         subject.getPrincipals().add(principal);
283         return principal;
284     }
285
286     /**
287      * Indicates that the overall login failed, and the server should release
288      * any resources associated with the user ID.
289      */

290     public void loginFailed(JaasSessionId sessionHandle) {
291         activeLogins.remove(sessionHandle);
292     }
293
294     /**
295      * Indicates that the client has logged out, and the server should release
296      * any resources associated with the user ID.
297      */

298     public void logout(JaasSessionId sessionHandle) throws LoginException JavaDoc {
299         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
300         if (session == null) {
301             throw new ExpiredLoginModuleException();
302         }
303         ContextManager.unregisterSubject(session.getSubject());
304         activeLogins.remove(sessionHandle);
305         for (int i = 0; i < session.getModules().length; i++) {
306             if (session.isServerSide(i)) {
307                 session.getLoginModule(i).logout();
308             }
309         }
310     }
311
312     /**
313      * Syncs the shared state that's on thye client with the shared state that
314      * is on the server.
315      *
316      * @param sessionHandle
317      * @param sharedState the shared state that is on the client
318      * @return the sync'd shared state that is on the server
319      */

320     public Map JavaDoc syncShareState(JaasSessionId sessionHandle, Map JavaDoc sharedState) throws LoginException JavaDoc {
321         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
322         if (session == null) {
323             throw new ExpiredLoginModuleException();
324         }
325         session.getSharedContext().putAll(sharedState);
326         return LoginUtils.getSerializableCopy(session.getSharedContext());
327     }
328
329     /**
330      * Syncs the set of principals that are on the client with the set of principals that
331      * are on the server.
332      *
333      * @param sessionHandle
334      * @param principals the set of principals that are on the client side
335      * @return the sync'd set of principals that are on the server
336      */

337     public Set JavaDoc syncPrincipals(JaasSessionId sessionHandle, Set JavaDoc principals) throws LoginException JavaDoc {
338         JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(sessionHandle);
339         if (session == null) {
340             throw new ExpiredLoginModuleException();
341         }
342         session.getSubject().getPrincipals().addAll(principals);
343
344         return LoginUtils.getSerializableCopy(session.getSubject().getPrincipals());
345     }
346
347     private void checkContext(JaasSecuritySession session, int loginModuleIndex) throws LoginException JavaDoc {
348         if (session == null) {
349             throw new ExpiredLoginModuleException();
350         }
351         if (loginModuleIndex < 0 || loginModuleIndex >= session.getModules().length || !session.isServerSide(loginModuleIndex)) {
352             throw new LoginException JavaDoc("Invalid login module specified");
353         }
354     }
355
356     /**
357      * Prepares a new security context for a new client. Each client uses a
358      * unique security context to sture their authentication progress,
359      * principals, etc.
360      *
361      * @param realm The realm the client is authenticating to
362      */

363     private JaasSessionId initializeClient(SecurityRealm realm) {
364         long id;
365         synchronized (JaasLoginService.class) {
366             id = ++nextLoginModuleId;
367         }
368         JaasSessionId sessionHandle = new JaasSessionId(id, hash(id));
369         JaasLoginModuleConfiguration[] modules = realm.getAppConfigurationEntries();
370         JaasSecuritySession session = new JaasSecuritySession(realm.getRealmName(), modules, new HashMap JavaDoc(), classLoader);
371         activeLogins.put(sessionHandle, session);
372         return sessionHandle;
373     }
374
375     private SecurityRealm getRealm(String JavaDoc realmName) {
376         for (Iterator JavaDoc it = realms.iterator(); it.hasNext();) {
377             SecurityRealm test = (SecurityRealm) it.next();
378             if (test.getRealmName().equals(realmName)) {
379                 return test;
380             }
381         }
382         return null;
383     }
384
385     /**
386      * Hashes a unique ID. The client keeps an object around with the ID and
387      * the hash of the ID. That way it's not so easy to forge an ID and steal
388      * someone else's account.
389      */

390     private byte[] hash(long id) {
391         byte[] bytes = new byte[8];
392         for (int i = 7; i >= 0; i--) {
393             bytes[i] = (byte) (id);
394             id >>>= 8;
395         }
396
397         try {
398             Mac mac = Mac.getInstance(algorithm);
399             mac.init(key);
400             mac.update(bytes);
401
402             return mac.doFinal();
403         } catch (NoSuchAlgorithmException JavaDoc e) {
404         } catch (InvalidKeyException JavaDoc e) {
405         }
406         assert false : "Should never have reached here";
407         return null;
408     }
409
410     private class ExpirationMonitor extends TimerTask JavaDoc { //todo: different timeouts per realm?
411

412         public void run() {
413             long now = System.currentTimeMillis();
414             List JavaDoc list = new LinkedList JavaDoc();
415             synchronized (activeLogins) {
416                 for (Iterator JavaDoc it = activeLogins.keySet().iterator(); it.hasNext();) {
417                     JaasSessionId id = (JaasSessionId) it.next();
418                     JaasSecuritySession session = (JaasSecuritySession) activeLogins.get(id);
419                     int age = (int) (now - session.getCreated());
420                     if (session.isDone() || age > maxLoginDurationMillis) {
421                         list.add(session);
422                         session.setDone(true);
423                         it.remove();
424                     }
425                 }
426             }
427             for (Iterator JavaDoc it = list.iterator(); it.hasNext();) {
428                 JaasSecuritySession session = (JaasSecuritySession) it.next();
429                 ContextManager.unregisterSubject(session.getSubject());
430             }
431         }
432     }
433
434
435     // This stuff takes care of making this object into a GBean
436
public static final GBeanInfo GBEAN_INFO;
437
438     static {
439         GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(JaasLoginService.class, "JaasLoginService");
440
441         infoFactory.addAttribute("algorithm", String JavaDoc.class, true);
442         infoFactory.addAttribute("password", String JavaDoc.class, true);
443         infoFactory.addAttribute("classLoader", ClassLoader JavaDoc.class, false);
444         infoFactory.addAttribute("maxLoginDurationMillis", int.class, true);
445         infoFactory.addAttribute("expiredLoginScanIntervalMillis", int.class, true);
446         infoFactory.addAttribute("objectName", String JavaDoc.class, false);
447
448         infoFactory.addOperation("connectToRealm", new Class JavaDoc[]{String JavaDoc.class});
449         infoFactory.addOperation("getLoginConfiguration", new Class JavaDoc[]{JaasSessionId.class});
450         infoFactory.addOperation("getServerLoginCallbacks", new Class JavaDoc[]{JaasSessionId.class, int.class});
451         infoFactory.addOperation("performLogin", new Class JavaDoc[]{JaasSessionId.class, int.class, Callback JavaDoc[].class});
452         infoFactory.addOperation("performCommit", new Class JavaDoc[]{JaasSessionId.class, int.class});
453         infoFactory.addOperation("loginSucceeded", new Class JavaDoc[]{JaasSessionId.class});
454         infoFactory.addOperation("loginFailed", new Class JavaDoc[]{JaasSessionId.class});
455         infoFactory.addOperation("logout", new Class JavaDoc[]{JaasSessionId.class});
456         infoFactory.addOperation("syncShareState", new Class JavaDoc[]{JaasSessionId.class, Map JavaDoc.class});
457         infoFactory.addOperation("syncPrincipals", new Class JavaDoc[]{JaasSessionId.class, Set JavaDoc.class});
458
459         infoFactory.addReference("Realms", SecurityRealm.class, NameFactory.SECURITY_REALM);
460         infoFactory.addInterface(JaasLoginServiceMBean.class);
461
462         infoFactory.setConstructor(new String JavaDoc[]{"algorithm", "password", "classLoader", "objectName"});
463
464         GBEAN_INFO = infoFactory.getBeanInfo();
465     }
466
467     public static GBeanInfo getGBeanInfo() {
468         return GBEAN_INFO;
469     }
470 }
471
Popular Tags