KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > enterprise > security > web > SingleSignOn


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.web;
25
26 import java.io.IOException JavaDoc;
27 import java.security.Principal JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.logging.Logger JavaDoc;
32 import java.util.logging.Level JavaDoc;
33
34 import javax.servlet.ServletException JavaDoc;
35 import javax.servlet.http.Cookie JavaDoc;
36 import javax.servlet.http.HttpServletRequest JavaDoc;
37 import javax.servlet.http.HttpServletResponse JavaDoc;
38
39 import org.apache.catalina.Container;
40 import org.apache.catalina.HttpRequest;
41 import org.apache.catalina.HttpResponse;
42 import org.apache.catalina.Lifecycle;
43 import org.apache.catalina.LifecycleException;
44 import org.apache.catalina.Request;
45 import org.apache.catalina.Response;
46 import org.apache.catalina.Session;
47 import org.apache.catalina.SessionEvent;
48 import org.apache.catalina.SessionListener;
49 import org.apache.catalina.authenticator.Constants;
50 import org.apache.catalina.Realm;
51
52 import com.sun.enterprise.web.logging.pwc.LogDomains;
53 import java.util.concurrent.atomic.AtomicInteger JavaDoc;
54
55 /**
56  * A <strong>Valve</strong> that supports a "single sign on" user experience,
57  * where the security identity of a user who successfully authenticates to one
58  * web application is propogated to other web applications in the same
59  * security domain.
60  *
61  * @author Jyri Virkki (first implementation)
62  * @author Jean-Francois Arcand
63  */

64 public class SingleSignOn
65     extends org.apache.catalina.authenticator.SingleSignOn
66     implements Lifecycle, SessionListener, Runnable JavaDoc, SingleSignOnMBean {
67
68     // ----------------------------------------------------- Instance Variables
69

70     /**
71      * The log used by this class.
72      */

73     Logger JavaDoc logger = LogDomains.getLogger(LogDomains.PWC_LOGGER);
74
75     /**
76      * The background thread.
77      */

78     private Thread JavaDoc thread = null;
79
80     /**
81      * The background thread completion semaphore.
82      */

83     private boolean threadDone = false;
84         
85     /**
86      * The interval (in seconds) between checks for expired sessions.
87      */

88     private int ssoReapInterval = 60;
89
90     /**
91      * Max idle time (in seconds) for SSO entries before being elegible
92      * for purging.
93      * A value less than zero indicates that SSO entries are supposed
94      * to never expire.
95      */

96     private int ssoMaxInactive = 300;
97
98     //-------------------------------------------------------------- Monitoring
99

100     /**
101      * Number of cache hits
102      */

103     private AtomicInteger JavaDoc hitCount = new AtomicInteger JavaDoc(0);
104     
105     /**
106      * Number of cache misses
107      */

108     private AtomicInteger JavaDoc missCount = new AtomicInteger JavaDoc(0);
109     
110     // ------------------------------------------------------------- Properties
111

112
113     /**
114      * Return expire thread interval (seconds)
115      */

116     public int getReapInterval() {
117         
118         return this.ssoReapInterval;
119         
120     }
121
122         
123     /**
124      * Set expire thread interval (seconds)
125      */

126     public void setReapInterval(int t) {
127
128         this.ssoReapInterval = t;
129
130     }
131
132
133     /**
134      * Return max idle time for SSO entries (seconds)
135      */

136     public int getMaxInactive() {
137         
138         return this.ssoMaxInactive;
139
140     }
141
142     /**
143      * Set max idle time for SSO entries (seconds)
144      */

145     public void setMaxInactive(int t) {
146
147         this.ssoMaxInactive = t;
148
149     }
150
151
152     // ------------------------------------------------------ Lifecycle Methods
153

154
155     /**
156      * Prepare for the beginning of active use of the public methods of this
157      * component. This method should be called after <code>configure()</code>,
158      * and before any of the public methods of the component are utilized.
159      *
160      * @exception LifecycleException if this component detects a fatal error
161      * that prevents this component from being used
162      */

163     public void start() throws LifecycleException {
164
165         super.start();
166         // Start the background reaper thread
167
threadStart();
168
169     }
170
171
172     /**
173      * Gracefully terminate the active use of the public methods of this
174      * component. This method should be the last one called on a given
175      * instance of this component.
176      *
177      * @exception LifecycleException if this component detects a fatal error
178      * that needs to be reported
179      */

180     public void stop() throws LifecycleException {
181
182         super.stop();
183         // Stop the background reaper thread
184
threadStop();
185     }
186
187
188     // ------------------------------------------------ SessionListener Methods
189

190
191     /**
192      * Acknowledge the occurrence of the specified event.
193      *
194      * @param event SessionEvent that has occurred
195      */

196     public void sessionEvent(SessionEvent event) {
197
198         // We only care about session destroyed events
199
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()))
200             return;
201
202         // Look up the single session id associated with this session (if any)
203
Session session = event.getSession();
204         //S1AS8 6155481 START
205
if (logger.isLoggable(Level.FINE)) {
206             logger.fine("Process session destroyed on " + session);
207         }
208         //S1AS8 6155481 END
209
String JavaDoc ssoId = null;
210         synchronized (reverse) {
211             ssoId = (String JavaDoc) reverse.get(session);
212         }
213         if (ssoId == null) {
214             return;
215         }
216   
217         // Was the session destroyed as the result of a timeout?
218
// If so, we'll just remove the expired session from the
219
// SSO. If the session was logged out, we'll log out
220
// of all session associated with the SSO.
221
if (session.hasExpired()) {
222             removeSession(ssoId, session);
223         } else {
224             // The session was logged out.
225
// Deregister this single session id, invalidating
226
// associated sessions
227
deregister(ssoId);
228         }
229     }
230
231
232     // ---------------------------------------------------------- Valve Methods
233

234
235     /**
236      * Perform single-sign-on support processing for this request.
237      *
238      * @param request The servlet request we are processing
239      * @param response The servlet response we are creating
240      * @param context The valve context used to invoke the next valve
241      * in the current processing pipeline
242      *
243      * @exception IOException if an input/output error occurs
244      * @exception ServletException if a servlet error occurs
245      */

246     /** IASRI 4665318
247     public void invoke(Request request, Response response,
248                        ValveContext context)
249         throws IOException, ServletException {
250      */

251     // START OF IASRI 4665318
252
public int invoke(Request JavaDoc request, Response JavaDoc response)
253         throws IOException JavaDoc, ServletException JavaDoc {
254     // END OF IASRI 4665318
255

256         // If this is not an HTTP request and response, just pass them on
257
/* GlassFish 6386229
258         if (!(request instanceof HttpRequest) ||
259                 !(response instanceof HttpResponse)) {
260             // START OF IASRI 4665318
261             // context.invokeNext(request, response);
262             // return;
263             return INVOKE_NEXT;
264             // END OF IASRI 4665318
265         }
266         */

267         HttpServletRequest JavaDoc hreq = (HttpServletRequest JavaDoc) request.getRequest();
268         HttpServletResponse JavaDoc hres =
269                         (HttpServletResponse JavaDoc) response.getResponse();
270         request.removeNote(Constants.REQ_SSOID_NOTE);
271
272         // Has a valid user already been authenticated?
273
//S1AS8 6155481 START
274
if (logger.isLoggable(Level.FINE)) {
275             logger.fine("Process request for '" + hreq.getRequestURI() + "'");
276         }
277         if (hreq.getUserPrincipal() != null) {
278             //S1AS8 6155481 START
279
if (logger.isLoggable(Level.FINE)) {
280                 logger.fine(" Principal '" + hreq.getUserPrincipal().getName()
281                             + "' has already been authenticated");
282             }
283             // START OF IASRI 4665318
284
// context.invokeNext(request, response);
285
// return;
286
return INVOKE_NEXT;
287             // END OF IASRI 4665318
288
}
289
290         // Check for the single sign on cookie
291
//S1AS8 6155481 START
292
if (logger.isLoggable(Level.FINE)) {
293             logger.fine(" Checking for SSO cookie");
294         }
295         Cookie JavaDoc cookie = null;
296         Cookie JavaDoc cookies[] = hreq.getCookies();
297         if (cookies == null)
298             cookies = new Cookie JavaDoc[0];
299         for (int i = 0; i < cookies.length; i++) {
300             if (Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())) {
301                 cookie = cookies[i];
302                 break;
303             }
304         }
305         if (cookie == null) {
306             //S1AS8 6155481 START
307
if (logger.isLoggable(Level.FINE)) {
308                 logger.fine(" SSO cookie is not present");
309             }
310             //S1AS8 6155481 END
311
// START OF IASRI 4665318
312
// context.invokeNext(request, response);
313
// return;
314
return INVOKE_NEXT;
315             // END OF IASRI 4665318
316
}
317
318         // Get the realm associated with the app of this request.
319
// If there is no realm available, do not process SSO.
320
Realm realm = request.getContext().getRealm();
321         if (realm == null) {
322             //S1AS8 6155481 START
323
if (logger.isLoggable(Level.FINE)) {
324                 logger.fine(" No realm configured for this application, SSO "
325                             + "does not apply.");
326             }
327             //S1AS8 6155481 END
328
// START OF IASRI 4665318
329
// context.invokeNext(request, response);
330
// return;
331
return INVOKE_NEXT;
332             // END OF IASRI 4665318
333
}
334          
335         String JavaDoc realmName = realm.getRealmName();
336         if (realmName == null) {
337             //S1AS8 6155481 START
338
if (logger.isLoggable(Level.FINE)) {
339                 logger.fine(" No realm configured for this application, SSO "
340                             + "does not apply.");
341             }
342             //S1AS8 6155481 END
343
// START OF IASRI 4665318
344
// context.invokeNext(request, response);
345
// return;
346
return INVOKE_NEXT;
347             // END OF IASRI 4665318
348
}
349          
350         if (debug >= 1) {
351             //S1AS8 6155481 START
352
if (logger.isLoggable(Level.FINE)) {
353                 logger.fine("This application uses realm '" + realmName + "'");
354             }
355          }
356         //S1AS8 6155481 END
357

358         // Look up the cached Principal associated with this cookie value
359
//S1AS8 6155481 START
360
if (logger.isLoggable(Level.FINE)) {
361             logger.fine(" Checking for cached principal for "
362                         + cookie.getValue());
363         }
364         SingleSignOnEntry entry = lookupEntry(cookie.getValue());
365         if (entry != null) {
366             if (logger.isLoggable(Level.FINE)) {
367                 logger.fine(" Found cached principal '"
368                             + entry.principal.getName()
369                             + "' with auth type '" + entry.authType
370                             + "' in realm '" + entry.realmName + "'");
371             }
372             //S1AS8 6155481 END
373

374             // only use this SSO identity if it was set in the same realm
375
if (entry.realmName.equals(realmName)) {
376                 request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
377                 ((HttpRequest) request).setAuthType(entry.authType);
378                 ((HttpRequest) request).setUserPrincipal(entry.principal);
379                 // Touch the SSO entry access time
380
entry.lastAccessTime = System.currentTimeMillis();
381                 // update hit atomic counter
382
hitCount.incrementAndGet();
383             } else {
384                 //S1AS8 6155481 START
385
if (logger.isLoggable(Level.FINE)) {
386                     logger.fine(" Ignoring SSO entry which does not match "
387                                 + "application realm '" + realmName + "'");
388                 }
389                 // consider this a cache miss, update atomic counter
390
missCount.incrementAndGet();
391             }
392         } else {
393             if (logger.isLoggable(Level.FINE)) {
394                 logger.fine(" No cached principal found, erasing SSO cookie");
395             }
396             cookie.setMaxAge(0);
397             hres.addCookie(cookie);
398             //update miss atomic counter
399
missCount.incrementAndGet();
400         }
401         //S1AS8 6155481 END
402
// Invoke the next Valve in our pipeline
403
// START OF IASRI 4665318
404
// context.invokeNext(request, response);
405
// return;
406
return INVOKE_NEXT;
407         // END OF IASRI 4665318
408

409     }
410
411
412     // -------------------------------------------------------- Package Methods
413

414
415     /**
416      * Associate the specified single sign on identifier with the
417      * specified Session.
418      *
419      * @param ssoId Single sign on identifier
420      * @param session Session to be associated
421      */

422     protected void associate(String JavaDoc ssoId, Session session) {
423
424         //S1AS8 6155481 START
425
if (logger.isLoggable(Level.FINE)) {
426             logger.fine("Associate sso id " + ssoId + " with session "
427                         + session);
428         }
429         //S1AS8 6155481 END
430
SingleSignOnEntry sso = lookupEntry(ssoId);
431         if (sso != null)
432             sso.addSession(this, session);
433         synchronized (reverse) {
434             reverse.put(session, ssoId);
435         }
436
437     }
438
439     /**
440      * Deregister the specified session. If it is the last session,
441      * then also get rid of the single sign on identifier
442      *
443      * @param ssoId Single sign on identifier
444      * @param session Session to be deregistered
445      */

446     protected void deregister(String JavaDoc ssoId, Session session) {
447
448         synchronized (reverse) {
449             reverse.remove(session);
450         }
451
452         SingleSignOnEntry sso = lookupEntry(ssoId);
453         if ( sso == null )
454             return;
455
456         sso.removeSession( session );
457
458         // see if we are the last session, if so blow away ssoId
459
Session sessions[] = sso.findSessions();
460         if ( sessions == null || sessions.length == 0 ) {
461             synchronized (cache) {
462                 sso = (SingleSignOnEntry) cache.remove(ssoId);
463             }
464         }
465
466     }
467
468
469     /**
470      * Deregister the specified single sign on identifier, and invalidate
471      * any associated sessions.
472      *
473      * @param ssoId Single sign on identifier to deregister
474      */

475     protected void deregister(String JavaDoc ssoId) {
476
477         //S1AS8 6155481 START
478
if (logger.isLoggable(Level.FINE)) {
479             logger.fine("Deregistering sso id '" + ssoId + "'");
480         }
481         //S1AS8 6155481 END
482
// Look up and remove the corresponding SingleSignOnEntry
483
SingleSignOnEntry sso = null;
484         synchronized (cache) {
485             sso = (SingleSignOnEntry) cache.remove(ssoId);
486         }
487
488         if (sso == null)
489             return;
490
491         // Expire any associated sessions
492
Session sessions[] = sso.findSessions();
493         for (int i = 0; i < sessions.length; i++) {
494             if (logger.isLoggable(Level.FINE)) {
495                 logger.fine(" Invalidating session " + sessions[i]);
496             }
497             // Remove from reverse cache first to avoid recursion
498
synchronized (reverse) {
499                 reverse.remove(sessions[i]);
500             }
501             // Invalidate this session
502
sessions[i].expire();
503         }
504
505         // NOTE: Clients may still possess the old single sign on cookie,
506
// but it will be removed on the next request since it is no longer
507
// in the cache
508
}
509
510
511     /**
512      * Register the specified Principal as being associated with the specified
513      * value for the single sign on identifier.
514      *
515      * @param ssoId Single sign on identifier to register
516      * @param principal Associated user principal that is identified
517      * @param authType Authentication type used to authenticate this
518      * user principal
519      * @param username Username used to authenticate this user
520      * @param password Password used to authenticate this user
521      */

522     protected void register(String JavaDoc ssoId, Principal JavaDoc principal, String JavaDoc authType,
523                             String JavaDoc username, String JavaDoc password,
524                             String JavaDoc realmName) {
525
526         //S1AS8 6155481 START
527
if (logger.isLoggable(Level.FINE)) {
528             logger.fine("Registering sso id '" + ssoId + "' for user '"
529                         + principal.getName() + " in realm " + realmName
530                         + "' with auth type '" + authType + "'");
531         }
532         //S1AS8 6155481 END
533
synchronized (cache) {
534             cache.put(ssoId, new SingleSignOnEntry(principal,
535                                                    authType,
536                                                    username,
537                                                    password,
538                                                    realmName));
539         }
540     }
541
542
543     // ------------------------------------------------------ Protected Methods
544

545
546     /**
547      * Look up and return the cached SingleSignOn entry associated with this
548      * sso id value, if there is one; otherwise return <code>null</code>.
549      *
550      * @param ssoId Single sign on identifier to look up
551      */

552     protected SingleSignOnEntry lookupEntry(String JavaDoc ssoId) {
553
554         synchronized (cache) {
555             return ((SingleSignOnEntry) cache.get(ssoId));
556         }
557
558     }
559
560
561     /**
562      * Invalidate all SSO cache entries that have expired.
563      */

564     private void processExpires() {
565
566         if (ssoMaxInactive < 0) {
567             // SSO entries are supposed to never expire
568
return;
569         }
570
571         long tooOld = System.currentTimeMillis() - ssoMaxInactive * 1000;
572         //S1AS8 6155481 START
573
if (logger.isLoggable(Level.FINE)) {
574             logger.fine("SSO expiration started. Current entries: "
575                         + cache.size());
576         }
577         //S1AS8 6155481 END
578
ArrayList JavaDoc removals = new ArrayList JavaDoc(cache.size()/2);
579         
580         // build list of removal targets
581

582         // Note that only those SSO entries which are NOT associated with
583
// any session are elegible for removal here.
584
// Currently no session association ever happens so this covers all
585
// SSO entries. However, this should be addressed separately.
586

587         try {
588             synchronized (cache) {
589
590                 Iterator JavaDoc it = cache.keySet().iterator();
591                 while (it.hasNext()) {
592                     String JavaDoc key = (String JavaDoc) it.next();
593                     SingleSignOnEntry sso = (SingleSignOnEntry) cache.get(key);
594                     if (sso.sessions.length == 0 &&
595                         sso.lastAccessTime < tooOld) {
596                         removals.add(key);
597                     }
598                 }
599             }
600
601             int removalCount = removals.size();
602             //S1AS8 6155481 START
603
if (logger.isLoggable(Level.FINE)) {
604                 logger.fine("SSO cache will expire " + removalCount
605                             + " entries.");
606             }
607             //S1AS8 6155481 END
608
// deregister any elegible sso entries
609
for (int i=0; i < removalCount; i++) {
610                 //S1AS8 6155481 START
611
if (logger.isLoggable(Level.FINE)) {
612                     logger.fine("SSO expiration removing entry: "
613                                 + removals.get(i));
614                 }
615                 deregister((String JavaDoc)removals.get(i));
616             }
617             //S1AS8 6155481 END
618
} catch (Throwable JavaDoc e) { // don't let thread die
619
logger.warning("Caught exception during SingleSignOn expiration: "
620                            + e);
621         }
622     }
623
624
625     /**
626      * Sleep for the duration specified by the <code>ssoReapInterval</code>
627      * property.
628      */

629     private void threadSleep() {
630
631         try {
632             Thread.sleep(ssoReapInterval * 1000L);
633         } catch (InterruptedException JavaDoc e) {
634             ;
635         }
636
637     }
638
639         
640    /**
641      * Start the background thread that will periodically check for
642      * SSO timeouts.
643      */

644     private void threadStart() {
645
646         if (thread != null)
647             return;
648
649         threadDone = false;
650         String JavaDoc threadName = "SingleSignOnExpiration";
651         thread = new Thread JavaDoc(this, threadName);
652         thread.setDaemon(true);
653         thread.start();
654
655     }
656
657
658     /**
659      * Stop the background thread that is periodically checking for
660      * SSO timeouts.
661      */

662     private void threadStop() {
663
664         if (thread == null)
665             return;
666
667         threadDone = true;
668         thread.interrupt();
669         try {
670             thread.join();
671         } catch (InterruptedException JavaDoc e) {
672             ;
673         }
674
675         thread = null;
676
677     }
678
679
680     // ------------------------------------------------------ Background Thread
681

682
683     /**
684      * The background thread that checks for SSO timeouts and shutdown.
685      */

686     public void run() {
687
688         // Loop until the termination semaphore is set
689
while (!threadDone) {
690             threadSleep();
691             processExpires();
692         }
693
694     }
695         
696     /**
697      * Remove a single Session from a SingleSignOn. Called when
698      * a session is timed out and no longer active.
699      *
700      * @param ssoId Single sign on identifier from which to remove the session.
701      * @param session the session to be removed.
702      */

703     protected void removeSession(String JavaDoc ssoId, Session session) {
704
705         if (logger.isLoggable(Level.FINE)) {
706             logger.fine("Removing session " + session.toString()
707                         + " from sso id " + ssoId );
708         }
709
710         // Get a reference to the SingleSignOn
711
SingleSignOnEntry entry = lookupEntry(ssoId);
712         if (entry == null)
713             return;
714
715         // Remove the inactive session from SingleSignOnEntry
716
entry.removeSession(session);
717
718         // Remove the inactive session from the 'reverse' Map.
719
synchronized(reverse) {
720             reverse.remove(session);
721         }
722
723         // If there are not sessions left in the SingleSignOnEntry,
724
// deregister the entry.
725
if (entry.findSessions().length == 0) {
726             deregister(ssoId);
727         }
728     }
729     
730     //-------------------------------------------------- Monitoring Support
731

732     /**
733      * Gets the number of sessions participating in SSO
734      *
735      * @return Number of sessions participating in SSO
736      */

737     public int getActiveSessionCount() {
738         return cache.size();
739     }
740
741     
742     /**
743      * Gets the number of SSO cache hits
744      *
745      * @return Number of SSO cache hits
746      */

747     public int getHitCount() {
748         return hitCount.intValue();
749     }
750
751     
752     /**
753      * Gets the number of SSO cache misses
754      *
755      * @return Number of SSO cache misses
756      */

757     public int getMissCount() {
758         return missCount.intValue();
759     }
760
761 }
762
763
764
Popular Tags