KickJava   Java API By Example, From Geeks To Geeks.

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


1 /**
2  *
3  * Copyright 2003-2004 The Apache Software Foundation
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * 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 package org.apache.geronimo.security.jaas;
18
19 import java.security.InvalidKeyException JavaDoc;
20 import java.security.NoSuchAlgorithmException JavaDoc;
21 import java.security.Principal JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Hashtable JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.LinkedList JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import javax.crypto.Mac;
31 import javax.crypto.SecretKey;
32 import javax.crypto.spec.SecretKeySpec;
33 import javax.management.ObjectName JavaDoc;
34 import javax.security.auth.Subject JavaDoc;
35 import javax.security.auth.callback.Callback JavaDoc;
36 import javax.security.auth.login.LoginException JavaDoc;
37 import javax.security.auth.spi.LoginModule JavaDoc;
38
39 import EDU.oswego.cs.dl.util.concurrent.ClockDaemon;
40 import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
41
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.geronimo.common.GeronimoSecurityException;
45 import org.apache.geronimo.gbean.GBeanInfo;
46 import org.apache.geronimo.gbean.GBeanInfoBuilder;
47 import org.apache.geronimo.gbean.GBeanLifecycle;
48 import org.apache.geronimo.gbean.ReferenceCollection;
49 import org.apache.geronimo.kernel.jmx.JMXUtil;
50 import org.apache.geronimo.security.ContextManager;
51 import org.apache.geronimo.security.IdentificationPrincipal;
52 import org.apache.geronimo.security.SubjectId;
53 import org.apache.geronimo.security.realm.SecurityRealm;
54 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
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: 46019 $ $Date: 2004-09-14 05:56:06 -0400 (Tue, 14 Sep 2004) $
63  */

64 public class JaasLoginService implements GBeanLifecycle, JaasLoginServiceMBean {
65     public static final ObjectName JavaDoc OBJECT_NAME = JMXUtil.getObjectName("geronimo.server:J2EEApplication=null,J2EEModule=org/apache/geronimo/Server,J2EEServer=geronimo,j2eeType=GBean,name=JaasLoginService");
66     public static final Log log = LogFactory.getLog(JaasLoginService.class);
67     private final static int DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL = 300000; // 5 mins
68
private final static int DEFAULT_MAX_LOGIN_DURATION = 1000 * 3600 * 24; // 1 day
69
private final static ClockDaemon clockDaemon;
70     private static long nextLoginModuleId = System.currentTimeMillis();
71     private ReferenceCollection realms;
72     private Object JavaDoc expiredLoginScanIdentifier;
73     private final SecretKey key;
74     private final String JavaDoc algorithm;
75     private final ClassLoader JavaDoc classLoader;
76     private final Map JavaDoc activeLogins = new Hashtable JavaDoc();
77     private int expiredLoginScanIntervalMillis = DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL;
78     private int maxLoginDurationMillis = DEFAULT_MAX_LOGIN_DURATION;
79
80     public JaasLoginService(String JavaDoc algorithm, String JavaDoc password, ClassLoader JavaDoc classLoader) {
81         this.classLoader = classLoader;
82         this.algorithm = algorithm;
83         //todo: password could just be randomly generated??
84
key = new SecretKeySpec(password.getBytes(), algorithm);
85     }
86
87     /**
88      * GBean property
89      */

90     public Collection JavaDoc getRealms() throws GeronimoSecurityException {
91         return realms;
92     }
93
94     /**
95      * GBean property
96      */

97     public void setRealms(Collection JavaDoc realms) {
98         this.realms = (ReferenceCollection) realms;
99         //todo: add listener to drop logins when realm is removed
100
}
101
102     /**
103      * GBean property
104      */

105     public int getMaxLoginDurationMillis() {
106         return maxLoginDurationMillis;
107     }
108
109     /**
110      * GBean property
111      */

112     public void setMaxLoginDurationMillis(int maxLoginDurationMillis) {
113         if(maxLoginDurationMillis == 0) {
114             maxLoginDurationMillis = DEFAULT_MAX_LOGIN_DURATION;
115         }
116         this.maxLoginDurationMillis = maxLoginDurationMillis;
117     }
118
119     /**
120      * GBean property
121      */

122     public int getExpiredLoginScanIntervalMillis() {
123         return expiredLoginScanIntervalMillis;
124     }
125
126     /**
127      * GBean property
128      */

129     public void setExpiredLoginScanIntervalMillis(int expiredLoginScanIntervalMillis) {
130         if(expiredLoginScanIntervalMillis == 0) {
131             expiredLoginScanIntervalMillis = DEFAULT_EXPIRED_LOGIN_SCAN_INTERVAL;
132         }
133         this.expiredLoginScanIntervalMillis = expiredLoginScanIntervalMillis;
134     }
135
136     public void doStart() throws Exception JavaDoc {
137         expiredLoginScanIdentifier = clockDaemon.executePeriodically(expiredLoginScanIntervalMillis, new ExpirationMonitor(), true);
138     }
139
140     public void doStop() throws Exception JavaDoc {
141         ClockDaemon.cancel(expiredLoginScanIdentifier);
142         //todo: shut down all logins
143
}
144
145     public void doFail() {
146         //todo: shut down all logins
147
}
148
149     /**
150      * Starts a new authentication process on behalf of an end user. The
151      * returned ID will identify that user throughout the user's interaction
152      * with the server. On the server side, that means maintaining the
153      * Subject and Principals for the user.
154      *
155      * @return The UserIdentifier used as an argument for the rest of the
156      * methods in this class.
157      */

158     public JaasClientId connectToRealm(String JavaDoc realmName) {
159         SecurityRealm realm = null;
160         realm = getRealm(realmName);
161         if(realm == null) {
162             throw new GeronimoSecurityException("No such realm ("+realmName+")");
163         } else {
164             return initializeClient(realm);
165         }
166     }
167
168     /**
169      * Gets the login module configuration for the specified realm. The
170      * caller needs that in order to perform the authentication process.
171      */

172     public JaasLoginModuleConfiguration[] getLoginConfiguration(JaasClientId userIdentifier) throws LoginException JavaDoc {
173         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
174         if(context == null) {
175             throw new ExpiredLoginModuleException();
176         }
177         JaasLoginModuleConfiguration[] config = context.getModules();
178         // strip out non-serializable configuration options
179
JaasLoginModuleConfiguration[] result = new JaasLoginModuleConfiguration[config.length];
180         for (int i = 0; i < config.length; i++) {
181             result[i] = config[i].getSerializableCopy();
182         }
183         return result;
184     }
185
186     /**
187      * Retrieves callbacks for a server side login module. When the client
188      * is going through the configured login modules, if a specific login
189      * module is client-side, it will be handled directly. If it is
190      * server-side, the client gets the callbacks (using this method),
191      * populates them, and sends them back to the server.
192      */

193     public Callback JavaDoc[] getServerLoginCallbacks(JaasClientId userIdentifier, int loginModuleIndex) throws LoginException JavaDoc {
194         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
195         if(context == null) {
196             throw new ExpiredLoginModuleException();
197         }
198         if(loginModuleIndex < 0 || loginModuleIndex >= context.getModules().length || !context.getModules()[loginModuleIndex].isServerSide()) {
199             throw new LoginException JavaDoc("Invalid login module specified");
200         }
201         JaasLoginModuleConfiguration config = context.getModules()[loginModuleIndex];
202         LoginModule JavaDoc module = config.getLoginModule(classLoader);
203         //todo: properly handle shared state
204
context.getHandler().setExploring();
205         try {
206             module.initialize(context.getSubject(), context.getHandler(), new HashMap JavaDoc(), config.getOptions());
207         } catch (Exception JavaDoc e) {
208             System.err.println("Failed to initialize module");
209             e.printStackTrace();
210         }
211         try {
212             module.login();
213         } catch (LoginException JavaDoc e) {}
214         try {
215             module.abort();
216         } catch(LoginException JavaDoc e) {}
217         return context.getHandler().finalizeCallbackList();
218     }
219
220     /**
221      * Returns populated callbacks for a server side login module. When the
222      * client is going through the configured login modules, if a specific
223      * login module is client-side, it will be handled directly. If it is
224      * server-side, the client gets the callbacks, populates them, and sends
225      * them back to the server (using this method).
226      */

227     public boolean performServerLogin(JaasClientId userIdentifier, int loginModuleIndex, Callback JavaDoc[] results) throws LoginException JavaDoc {
228         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
229         if(context == null) {
230             throw new ExpiredLoginModuleException();
231         }
232         if (loginModuleIndex < 0 || loginModuleIndex >= context.getModules().length || !context.getModules()[loginModuleIndex].isServerSide()) {
233             throw new LoginException JavaDoc("Invalid login module specified");
234         }
235         JaasLoginModuleConfiguration module = context.getModules()[loginModuleIndex];
236         try {
237             context.getHandler().setClientResponse(results);
238         } catch (IllegalArgumentException JavaDoc iae) {
239             throw new LoginException JavaDoc(iae.toString());
240         }
241         return module.getLoginModule(classLoader).login();
242     }
243
244     /**
245      * Indicates that the overall login succeeded, and some principals were
246      * generated by a client-side login module. This method needs to be called
247      * once for each client-side login module, to specify Principals for each
248      * module.
249      */

250     public void clientLoginModuleCommit(JaasClientId userIdentifier, int loginModuleIndex, Principal JavaDoc[] clientLoginModulePrincipals) throws LoginException JavaDoc {
251         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
252         if(context == null) {
253             throw new ExpiredLoginModuleException();
254         }
255         if(loginModuleIndex < 0 || loginModuleIndex >= context.getModules().length || context.getModules()[loginModuleIndex].isServerSide()) {
256             throw new LoginException JavaDoc("Invalid login module specified");
257         }
258         context.processPrincipals(clientLoginModulePrincipals, context.getModules()[loginModuleIndex].getLoginDomainName());
259     }
260
261     /**
262      * Indicates that the overall login succeeded, and a particular server-side
263      * login module should be committed. This method needs to be called
264      * once for each server-side login module that was processed before the
265      * overall authentication succeeded.
266      */

267     public boolean serverLoginModuleCommit(JaasClientId userIdentifier, int loginModuleIndex) throws LoginException JavaDoc {
268         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
269         if(context == null) {
270             throw new ExpiredLoginModuleException();
271         }
272         if(loginModuleIndex < 0 || loginModuleIndex >= context.getModules().length || !context.getModules()[loginModuleIndex].isServerSide()) {
273             throw new LoginException JavaDoc("Invalid login module specified");
274         }
275         JaasLoginModuleConfiguration module = context.getModules()[loginModuleIndex];
276         boolean result = module.getLoginModule(classLoader).commit();
277         context.processPrincipals(context.getModules()[loginModuleIndex].getLoginDomainName());
278         return result;
279     }
280
281     /**
282      * Indicates that the overall login succeeded. All login modules that were
283      * touched should have been logged in and committed before calling this.
284      */

285     public Principal JavaDoc[] loginSucceeded(JaasClientId userIdentifier) throws LoginException JavaDoc {
286         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
287         if(context == null) {
288             throw new ExpiredLoginModuleException();
289         }
290
291         Subject JavaDoc subject = context.getSubject();
292         ContextManager.registerSubject(subject);
293         SubjectId id = ContextManager.getSubjectId(subject);
294         IdentificationPrincipal principal = new IdentificationPrincipal(id);
295         subject.getPrincipals().add(principal);
296         SecurityRealm realm = getRealm(context.getRealmName());
297         if(realm.isRestrictPrincipalsToServer()) {
298             return new Principal JavaDoc[]{principal};
299         } else {
300             List JavaDoc list = new ArrayList JavaDoc();
301             list.addAll(context.getProcessedPrincipals());
302             list.add(principal);
303             return (Principal JavaDoc[]) list.toArray(new Principal JavaDoc[list.size()]);
304         }
305     }
306
307     /**
308      * Indicates that the overall login failed, and the server should release
309      * any resources associated with the user ID.
310      */

311     public void loginFailed(JaasClientId userIdentifier) {
312         activeLogins.remove(userIdentifier);
313     }
314
315     /**
316      * Indicates that the client has logged out, and the server should release
317      * any resources associated with the user ID.
318      */

319     public void logout(JaasClientId userIdentifier) throws LoginException JavaDoc {
320         JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(userIdentifier);
321         if(context == null) {
322             throw new ExpiredLoginModuleException();
323         }
324         ContextManager.unregisterSubject(context.getSubject());
325         activeLogins.remove(userIdentifier);
326         for (int i = 0; i < context.getModules().length; i++) {
327             if(context.getModules()[i].isServerSide()) {
328                 context.getModules()[i].getLoginModule(classLoader).logout();
329             }
330         }
331     }
332
333     /**
334      * Prepares a new security context for a new client. Each client uses a
335      * unique security context to sture their authentication progress,
336      * principals, etc.
337      *
338      * @param realm The realm the client is authenticating to
339      */

340     private JaasClientId initializeClient(SecurityRealm realm) {
341         long id;
342         synchronized(JaasLoginService.class) {
343             id = ++nextLoginModuleId;
344         }
345         JaasClientId clientId = new JaasClientId(id, hash(id));
346         JaasLoginModuleConfiguration[] modules = realm.getAppConfigurationEntries();
347         JaasSecurityContext context = new JaasSecurityContext(realm.getRealmName(), modules);
348         activeLogins.put(clientId, context);
349         return clientId;
350     }
351
352     private SecurityRealm getRealm(String JavaDoc realmName) {
353         for (Iterator JavaDoc it = realms.iterator(); it.hasNext();) {
354             SecurityRealm test = (SecurityRealm) it.next();
355             if(test.getRealmName().equals(realmName)) {
356                 return test;
357             }
358         }
359         return null;
360     }
361
362     /**
363      * Hashes a unique ID. The client keeps an object around with the ID and
364      * the hash of the ID. That way it's not so easy to forge an ID and steal
365      * someone else's account.
366      */

367     private byte[] hash(long id) {
368         byte[] bytes = new byte[8];
369         for (int i = 7; i >= 0; i--) {
370             bytes[i] = (byte) (id);
371             id >>>= 8;
372         }
373
374         try {
375             Mac mac = Mac.getInstance(algorithm);
376             mac.init(key);
377             mac.update(bytes);
378
379             return mac.doFinal();
380         } catch (NoSuchAlgorithmException JavaDoc e) {
381         } catch (InvalidKeyException JavaDoc e) {
382         }
383         assert false : "Should never have reached here";
384         return null;
385     }
386
387
388
389     // This stuff takes care of whacking old logins
390
static {
391         clockDaemon = new ClockDaemon();
392         clockDaemon.setThreadFactory(new ThreadFactory() {
393             public Thread JavaDoc newThread(Runnable JavaDoc r) {
394                 Thread JavaDoc t = new Thread JavaDoc(r, "LoginService login modules monitor");
395                 t.setDaemon(true);
396                 return t;
397             }
398         });
399     }
400     private class ExpirationMonitor implements Runnable JavaDoc { //todo: different timeouts per realm?
401
public void run() {
402             long now = System.currentTimeMillis();
403             List JavaDoc list = new LinkedList JavaDoc();
404             synchronized(activeLogins) {
405                 for (Iterator JavaDoc it = activeLogins.keySet().iterator(); it.hasNext();) {
406                     JaasClientId id = (JaasClientId) it.next();
407                     JaasSecurityContext context = (JaasSecurityContext) activeLogins.get(id);
408                     int age = (int)(now-context.getCreated());
409                     if(context.isDone() || age > maxLoginDurationMillis) {
410                         list.add(context);
411                         context.setDone(true);
412                         it.remove();
413                     }
414                 }
415             }
416             for (Iterator JavaDoc it = list.iterator(); it.hasNext();) {
417                 JaasSecurityContext context = (JaasSecurityContext) it.next();
418                 ContextManager.unregisterSubject(context.getSubject());
419             }
420         }
421     }
422
423
424
425     // This stuff takes care of making this object into a GBean
426
public static final GBeanInfo GBEAN_INFO;
427
428     static {
429         GBeanInfoBuilder infoFactory = new GBeanInfoBuilder(JaasLoginService.class); //just a gbean
430

431         infoFactory.addAttribute("algorithm", String JavaDoc.class, true);
432         infoFactory.addAttribute("password", String JavaDoc.class, true);
433         infoFactory.addAttribute("classLoader", ClassLoader JavaDoc.class, false);
434         infoFactory.addAttribute("maxLoginDurationMillis", int.class, true);
435         infoFactory.addAttribute("expiredLoginScanIntervalMillis", int.class, true);
436
437         infoFactory.addOperation("connectToRealm", new Class JavaDoc[]{String JavaDoc.class});
438         infoFactory.addOperation("getLoginConfiguration", new Class JavaDoc[]{JaasClientId.class});
439         infoFactory.addOperation("getServerLoginCallbacks", new Class JavaDoc[]{JaasClientId.class, int.class});
440         infoFactory.addOperation("performServerLogin", new Class JavaDoc[]{JaasClientId.class, int.class, Callback JavaDoc[].class});
441         infoFactory.addOperation("clientLoginModuleCommit", new Class JavaDoc[]{JaasClientId.class, int.class, Principal JavaDoc[].class});
442         infoFactory.addOperation("serverLoginModuleCommit", new Class JavaDoc[]{JaasClientId.class, int.class});
443         infoFactory.addOperation("loginSucceeded", new Class JavaDoc[]{JaasClientId.class});
444         infoFactory.addOperation("loginFailed", new Class JavaDoc[]{JaasClientId.class});
445         infoFactory.addOperation("logout", new Class JavaDoc[]{JaasClientId.class});
446
447         infoFactory.addReference("Realms", SecurityRealm.class, NameFactory.SECURITY_REALM);
448
449         infoFactory.setConstructor(new String JavaDoc[] {"algorithm", "password", "classLoader"});
450
451         GBEAN_INFO = infoFactory.getBeanInfo();
452     }
453
454     public static GBeanInfo getGBeanInfo() {
455         return GBEAN_INFO;
456     }
457 }
458
Popular Tags