KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > server > security > AbstractAuthenticator


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  * Free SoftwareFoundation, Inc.
23  * 59 Temple Place, Suite 330
24  * Boston, MA 02111-1307 USA
25  *
26  * @author Scott Ferguson
27  */

28
29 package com.caucho.server.security;
30
31 import com.caucho.log.Log;
32 import com.caucho.security.BasicPrincipal;
33 import com.caucho.server.session.SessionImpl;
34 import com.caucho.server.session.SessionManager;
35 import com.caucho.server.webapp.Application;
36 import com.caucho.util.Alarm;
37 import com.caucho.util.CharBuffer;
38 import com.caucho.util.L10N;
39 import com.caucho.util.LruCache;
40
41 import javax.annotation.PostConstruct;
42 import javax.servlet.ServletContext JavaDoc;
43 import javax.servlet.ServletException JavaDoc;
44 import javax.servlet.http.HttpServletRequest JavaDoc;
45 import javax.servlet.http.HttpServletResponse JavaDoc;
46 import javax.servlet.http.HttpSession JavaDoc;
47 import java.lang.ref.SoftReference JavaDoc;
48 import java.security.MessageDigest JavaDoc;
49 import java.security.Principal JavaDoc;
50 import java.util.ArrayList JavaDoc;
51 import java.util.logging.Level JavaDoc;
52 import java.util.logging.Logger JavaDoc;
53
54 /**
55  * All applications should extend AbstractAuthenticator to implement
56  * their custom authenticators. While this isn't absolutely required,
57  * it protects implementations from API changes.
58  *
59  * <p>The AbstractAuthenticator provides a single-signon cache. Users
60  * logged into one web-app will share the same principal.
61  */

62 public class AbstractAuthenticator implements ServletAuthenticator {
63   static final Logger JavaDoc log = Log.open(AbstractAuthenticator.class);
64   static final L10N L = new L10N(AbstractAuthenticator.class);
65   
66   public static final String JavaDoc LOGIN_NAME = "com.caucho.servlet.login.name";
67   
68   
69   protected int _principalCacheSize = 4096;
70   protected LruCache<String JavaDoc,PrincipalEntry> _principalCache;
71
72   protected String JavaDoc _passwordDigestAlgorithm = "MD5-base64";
73   protected String JavaDoc _passwordDigestRealm = "resin";
74   protected PasswordDigest _passwordDigest;
75
76   private boolean _logoutOnTimeout = true;
77
78   /**
79    * Returns the size of the principal cache.
80    */

81   public int getPrincipalCacheSize()
82   {
83     return _principalCacheSize;
84   }
85
86   /**
87    * Sets the size of the principal cache.
88    */

89   public void setPrincipalCacheSize(int size)
90   {
91     _principalCacheSize = size;
92   }
93
94   /**
95    * Returns the password digest
96    */

97   public PasswordDigest getPasswordDigest()
98   {
99     return _passwordDigest;
100   }
101
102   /**
103    * Sets the password digest. The password digest of the form:
104    * "algorithm-format", e.g. "MD5-base64".
105    */

106   public void setPasswordDigest(PasswordDigest digest)
107   {
108     _passwordDigest = digest;
109   }
110
111   /**
112    * Returns the password digest algorithm
113    */

114   public String JavaDoc getPasswordDigestAlgorithm()
115   {
116     return _passwordDigestAlgorithm;
117   }
118
119   /**
120    * Sets the password digest algorithm. The password digest of the form:
121    * "algorithm-format", e.g. "MD5-base64".
122    */

123   public void setPasswordDigestAlgorithm(String JavaDoc digest)
124   {
125     _passwordDigestAlgorithm = digest;
126   }
127
128   /**
129    * Returns the password digest realm
130    */

131   public String JavaDoc getPasswordDigestRealm()
132   {
133     return _passwordDigestRealm;
134   }
135
136   /**
137    * Sets the password digest realm.
138    */

139   public void setPasswordDigestRealm(String JavaDoc realm)
140   {
141     _passwordDigestRealm = realm;
142   }
143
144   /**
145    * Returns true if the user should be logged out on a session timeout.
146    */

147   public boolean getLogoutOnSessionTimeout()
148   {
149     return _logoutOnTimeout;
150   }
151
152   /**
153    * Sets true if the principal should logout when the session times out.
154    */

155   public void setLogoutOnSessionTimeout(boolean logout)
156   {
157     _logoutOnTimeout = logout;
158   }
159
160   /**
161    * Adds a role mapping.
162    */

163   public void addRoleMapping(Principal principal, String JavaDoc role)
164   {
165   }
166
167   /**
168    * Initialize the authenticator with the application.
169    */

170   @PostConstruct
171   public void init()
172     throws ServletException JavaDoc
173   {
174     if (_principalCacheSize > 0)
175       _principalCache = new LruCache<String JavaDoc,PrincipalEntry>(_principalCacheSize);
176
177     if (_passwordDigest != null) {
178       if (_passwordDigest.getAlgorithm() == null ||
179       _passwordDigest.getAlgorithm().equals("none"))
180     _passwordDigest = null;
181     }
182     else if (_passwordDigestAlgorithm == null ||
183          _passwordDigestAlgorithm.equals("none")) {
184     }
185     else {
186       int p = _passwordDigestAlgorithm.indexOf('-');
187
188       if (p > 0) {
189         String JavaDoc algorithm = _passwordDigestAlgorithm.substring(0, p);
190         String JavaDoc format = _passwordDigestAlgorithm.substring(p + 1);
191
192         _passwordDigest = new PasswordDigest();
193         _passwordDigest.setAlgorithm(algorithm);
194         _passwordDigest.setFormat(format);
195         _passwordDigest.setRealm(_passwordDigestRealm);
196
197         _passwordDigest.init();
198       }
199     }
200   }
201
202   /**
203    * Logs the user in with any appropriate password.
204    */

205   public Principal login(HttpServletRequest JavaDoc request,
206                          HttpServletResponse JavaDoc response,
207                          ServletContext JavaDoc app,
208                          String JavaDoc user, String JavaDoc password)
209     throws ServletException JavaDoc
210   {
211     String JavaDoc digestPassword = getPasswordDigest(request, response, app,
212                           user, password);
213
214     Principal principal = loginImpl(request, response, app, user,
215                     digestPassword);
216
217     if (principal != null) {
218       SessionImpl session = (SessionImpl) request.getSession();
219       session.setUser(principal);
220
221       if (_principalCache != null) {
222     PrincipalEntry entry = new PrincipalEntry(principal);
223     entry.addSession(session);
224     
225         _principalCache.put(session.getId(), entry);
226       }
227     }
228
229     return principal;
230   }
231
232   /**
233    * Returns the digest view of the password. The default
234    * uses the PasswordDigest class if available, and returns the
235    * plaintext password if not.
236    */

237   public String JavaDoc getPasswordDigest(HttpServletRequest JavaDoc request,
238                   HttpServletResponse JavaDoc response,
239                   ServletContext JavaDoc app,
240                   String JavaDoc user, String JavaDoc password)
241     throws ServletException JavaDoc
242   {
243
244     if (_passwordDigest != null)
245       return _passwordDigest.getPasswordDigest(request, response, app,
246                            user, password);
247     else
248       return password;
249   }
250
251   /**
252    * Authenticate (login) the user.
253    */

254   protected Principal loginImpl(HttpServletRequest JavaDoc request,
255                                 HttpServletResponse JavaDoc response,
256                                 ServletContext JavaDoc application,
257                                 String JavaDoc user, String JavaDoc password)
258     throws ServletException JavaDoc
259   {
260     return null;
261   }
262
263   
264   /**
265    * Validates the user when using HTTP Digest authentication.
266    * DigestLogin will call this method. Most other AbstractLogin
267    * implementations, like BasicLogin and FormLogin, will use
268    * getUserPrincipal instead.
269    *
270    * <p>The HTTP Digest authentication uses the following algorithm
271    * to calculate the digest. The digest is then compared to
272    * the client digest.
273    *
274    * <code><pre>
275    * A1 = MD5(username + ':' + realm + ':' + password)
276    * A2 = MD5(method + ':' + uri)
277    * digest = MD5(A1 + ':' + nonce + A2)
278    * </pre></code>
279    *
280    * @param request the request trying to authenticate.
281    * @param response the response for setting headers and cookies.
282    * @param app the servlet context
283    * @param user the username
284    * @param realm the authentication realm
285    * @param nonce the nonce passed to the client during the challenge
286    * @param uri te protected uri
287    * @param qop
288    * @param nc
289    * @param cnonce the client nonce
290    * @param clientDigest the client's calculation of the digest
291    *
292    * @return the logged in principal if successful
293    */

294   public Principal loginDigest(HttpServletRequest JavaDoc request,
295                                HttpServletResponse JavaDoc response,
296                                ServletContext JavaDoc app,
297                                String JavaDoc user, String JavaDoc realm,
298                                String JavaDoc nonce, String JavaDoc uri,
299                                String JavaDoc qop, String JavaDoc nc, String JavaDoc cnonce,
300                                byte []clientDigest)
301     throws ServletException JavaDoc
302   {
303     Principal principal = loginDigestImpl(request, response, app,
304                                           user, realm, nonce, uri,
305                                           qop, nc, cnonce,
306                                           clientDigest);
307
308     if (principal != null) {
309       SessionImpl session = (SessionImpl) request.getSession();
310       session.setUser(principal);
311
312       if (_principalCache != null) {
313     PrincipalEntry entry = new PrincipalEntry(principal);
314     entry.addSession(session);
315     
316         _principalCache.put(session.getId(), entry);
317       }
318     }
319
320     return principal;
321   }
322   
323   /**
324    * Validates the user when HTTP Digest authentication.
325    * The HTTP Digest authentication uses the following algorithm
326    * to calculate the digest. The digest is then compared to
327    * the client digest.
328    *
329    * <code><pre>
330    * A1 = MD5(username + ':' + realm + ':' + password)
331    * A2 = MD5(method + ':' + uri)
332    * digest = MD5(A1 + ':' + nonce + A2)
333    * </pre></code>
334    *
335    * @param request the request trying to authenticate.
336    * @param response the response for setting headers and cookies.
337    * @param app the servlet context
338    * @param user the username
339    * @param realm the authentication realm
340    * @param nonce the nonce passed to the client during the challenge
341    * @param uri te protected uri
342    * @param qop
343    * @param nc
344    * @param cnonce the client nonce
345    * @param clientDigest the client's calculation of the digest
346    *
347    * @return the logged in principal if successful
348    */

349   public Principal loginDigestImpl(HttpServletRequest JavaDoc request,
350                                    HttpServletResponse JavaDoc response,
351                                    ServletContext JavaDoc app,
352                                    String JavaDoc user, String JavaDoc realm,
353                                    String JavaDoc nonce, String JavaDoc uri,
354                                    String JavaDoc qop, String JavaDoc nc, String JavaDoc cnonce,
355                                    byte []clientDigest)
356     throws ServletException JavaDoc
357   {
358     
359     try {
360       if (clientDigest == null)
361     return null;
362       
363       MessageDigest JavaDoc digest = MessageDigest.getInstance("MD5");
364       
365       byte []a1 = getDigestSecret(request, response, app,
366                                   user, realm, "MD5");
367
368       if (a1 == null)
369         return null;
370
371       digestUpdateHex(digest, a1);
372       
373       digest.update((byte) ':');
374       for (int i = 0; i < nonce.length(); i++)
375         digest.update((byte) nonce.charAt(i));
376
377       if (qop != null) {
378         digest.update((byte) ':');
379         for (int i = 0; i < nc.length(); i++)
380           digest.update((byte) nc.charAt(i));
381
382         digest.update((byte) ':');
383
384         for (int i = 0; cnonce != null && i < cnonce.length(); i++)
385           digest.update((byte) cnonce.charAt(i));
386         
387         digest.update((byte) ':');
388         for (int i = 0; qop != null && i < qop.length(); i++)
389           digest.update((byte) qop.charAt(i));
390       }
391       digest.update((byte) ':');
392
393       byte []a2 = digest(request.getMethod() + ":" + uri);
394
395       digestUpdateHex(digest, a2);
396
397       byte []serverDigest = digest.digest();
398
399       if (clientDigest.length != serverDigest.length)
400         return null;
401
402       for (int i = 0; i < clientDigest.length; i++) {
403         if (serverDigest[i] != clientDigest[i])
404           return null;
405       }
406
407       return new BasicPrincipal(user);
408     } catch (Exception JavaDoc e) {
409       throw new ServletException JavaDoc(e);
410     }
411   }
412
413   private void digestUpdateHex(MessageDigest JavaDoc digest, byte []bytes)
414   {
415     for (int i = 0; i < bytes.length; i++) {
416       int b = bytes[i];
417       int d1 = (b >> 4) & 0xf;
418       int d2 = b & 0xf;
419
420       if (d1 < 10)
421         digest.update((byte) (d1 + '0'));
422       else
423         digest.update((byte) (d1 + 'a' - 10));
424
425       if (d2 < 10)
426         digest.update((byte) (d2 + '0'));
427       else
428         digest.update((byte) (d2 + 'a' - 10));
429     }
430   }
431
432   protected byte []stringToDigest(String JavaDoc digest)
433   {
434     if (digest == null)
435       return null;
436     
437     int len = (digest.length() + 1) / 2;
438     byte []clientDigest = new byte[len];
439
440     for (int i = 0; i + 1 < digest.length(); i += 2) {
441       int ch1 = digest.charAt(i);
442       int ch2 = digest.charAt(i + 1);
443
444       int b = 0;
445       if (ch1 >= '0' && ch1 <= '9')
446         b += ch1 - '0';
447       else if (ch1 >= 'a' && ch1 <= 'f')
448         b += ch1 - 'a' + 10;
449
450       b *= 16;
451       
452       if (ch2 >= '0' && ch2 <= '9')
453         b += ch2 - '0';
454       else if (ch2 >= 'a' && ch2 <= 'f')
455         b += ch2 - 'a' + 10;
456
457       clientDigest[i / 2] = (byte) b;
458     }
459
460     return clientDigest;
461   }
462
463   private String JavaDoc digestToString(byte []digest)
464   {
465     if (digest == null)
466       return "null";
467
468     CharBuffer cb = CharBuffer.allocate();
469     for (int i = 0; i < digest.length; i++) {
470       int ch = digest[i];
471
472       int d1 = (ch >> 4) & 0xf;
473       int d2 = ch & 0xf;
474
475       if (d1 < 10)
476         cb.append((char) (d1 + '0'));
477       else
478         cb.append((char) (d1 + 'a' - 10));
479         
480       if (d2 < 10)
481         cb.append((char) (d2 + '0'));
482       else
483         cb.append((char) (d2 + 'a' - 10));
484     }
485
486     return cb.close();
487   }
488
489   /**
490    * Returns the digest secret for Digest authentication.
491    */

492   protected byte []getDigestSecret(HttpServletRequest JavaDoc request,
493                                    HttpServletResponse JavaDoc response,
494                                    ServletContext JavaDoc application,
495                                    String JavaDoc username, String JavaDoc realm,
496                                    String JavaDoc algorithm)
497     throws ServletException JavaDoc
498   {
499     String JavaDoc password = getDigestPassword(request, response, application,
500                                         username, realm);
501     
502     if (password == null)
503       return null;
504
505     if (_passwordDigest != null)
506       return _passwordDigest.stringToDigest(password);
507
508     try {
509       MessageDigest JavaDoc digest = MessageDigest.getInstance(algorithm);
510
511       String JavaDoc string = username + ":" + realm + ":" + password;
512       byte []data = string.getBytes("UTF8");
513       return digest.digest(data);
514     } catch (Exception JavaDoc e) {
515       throw new ServletException JavaDoc(e);
516     }
517   }
518
519   protected byte []digest(String JavaDoc value)
520     throws ServletException JavaDoc
521   {
522     try {
523       MessageDigest JavaDoc digest = MessageDigest.getInstance("MD5");
524
525       byte []data = value.getBytes("UTF8");
526       return digest.digest(data);
527     } catch (Exception JavaDoc e) {
528       throw new ServletException JavaDoc(e);
529     }
530   }
531
532   /**
533    * Returns the password for authenticators too lazy to calculate the
534    * digest.
535    */

536   protected String JavaDoc getDigestPassword(HttpServletRequest JavaDoc request,
537                                      HttpServletResponse JavaDoc response,
538                                      ServletContext JavaDoc application,
539                                      String JavaDoc username, String JavaDoc realm)
540     throws ServletException JavaDoc
541   {
542     return null;
543   }
544
545   /**
546    * Grab the user from the request, assuming the user has
547    * already logged in. In other words, overriding methods could
548    * use cookies or the session to find the logged in principal, but
549    * shouldn't try to log the user in with form parameters.
550    *
551    * @param request the servlet request.
552    *
553    * @return a Principal representing the user or null if none has logged in.
554    */

555   public Principal getUserPrincipal(HttpServletRequest JavaDoc request,
556                                     HttpServletResponse JavaDoc response,
557                                     ServletContext JavaDoc application)
558     throws ServletException JavaDoc
559   {
560     SessionImpl session = (SessionImpl) request.getSession(false);
561     Principal user = null;
562
563     if (session != null)
564       user = session.getUser();
565     
566     if (user != null)
567       return user;
568
569     PrincipalEntry entry = null;
570     
571     if (_principalCache == null) {
572     }
573     else if (session != null)
574       entry = _principalCache.get(session.getId());
575     else if (request.getRequestedSessionId() != null)
576       entry = _principalCache.get(request.getRequestedSessionId());
577
578     if (entry != null) {
579       user = entry.getPrincipal();
580
581       if (session == null)
582     session = (SessionImpl) request.getSession(true);
583       
584       session.setUser(user);
585       entry.addSession(session);
586       
587       return user;
588     }
589
590     user = getUserPrincipalImpl(request, application);
591
592     if (user == null) {
593     }
594     else if (session != null) {
595       entry = new PrincipalEntry(user);
596       
597       session.setUser(user);
598       entry.addSession(session);
599       
600       _principalCache.put(session.getId(), entry);
601     }
602     else if (request.getRequestedSessionId() != null) {
603       entry = new PrincipalEntry(user);
604       
605       _principalCache.put(request.getRequestedSessionId(), entry);
606     }
607
608     return user;
609   }
610   
611   /**
612    * Gets the user from a persistent cookie, uaing authenticateCookie
613    * to actually look the cookie up.
614    */

615   protected Principal getUserPrincipalImpl(HttpServletRequest JavaDoc request,
616                                            ServletContext JavaDoc application)
617     throws ServletException JavaDoc
618   {
619     return null;
620   }
621
622   /**
623    * Returns true if the user plays the named role.
624    *
625    * @param request the servlet request
626    * @param user the user to test
627    * @param role the role to test
628    */

629   public boolean isUserInRole(HttpServletRequest JavaDoc request,
630                               HttpServletResponse JavaDoc response,
631                               ServletContext JavaDoc application,
632                               Principal user, String JavaDoc role)
633     throws ServletException JavaDoc
634   {
635     return false;
636   }
637
638   /**
639    * Logs the user out from the session.
640    *
641    * @param application the application
642    * @param timeoutSession the session timing out, null if not a timeout logout
643    * @param user the logged in user
644    */

645   public void logout(ServletContext JavaDoc application,
646              HttpSession JavaDoc timeoutSession,
647                      String JavaDoc sessionId,
648                      Principal user)
649     throws ServletException JavaDoc
650   {
651     log.fine("logout " + user);
652
653     if (sessionId != null) {
654       if (_principalCache == null) {
655       }
656       else if (timeoutSession != null) {
657     PrincipalEntry entry = _principalCache.get(sessionId);
658     
659     if (entry != null && entry.logout(timeoutSession)) {
660       _principalCache.remove(sessionId);
661     }
662       }
663       else {
664     PrincipalEntry entry = _principalCache.remove(sessionId);
665
666     if (entry != null)
667       entry.logout();
668       }
669
670       Application app = (Application) application;
671       SessionManager manager = app.getSessionManager();
672
673       if (manager != null) {
674     SessionImpl session = manager.getSession(sessionId,
675                          Alarm.getCurrentTime(),
676                          false, true);
677
678     if (session != null)
679       session.logout();
680       }
681     }
682   }
683
684   /**
685    * Logs the user out from the session.
686    *
687    * @param request the servlet request
688    * @deprecated
689    */

690   public void logout(HttpServletRequest JavaDoc request,
691                      HttpServletResponse JavaDoc response,
692                      ServletContext JavaDoc application,
693                      Principal user)
694     throws ServletException JavaDoc
695   {
696     logout(application, null, request.getRequestedSessionId(), user);
697   }
698
699   /**
700    * Logs the user out from the session.
701    *
702    * @param request the servlet request
703    * @deprecated
704    */

705   public void logout(ServletContext JavaDoc application,
706              String JavaDoc sessionId,
707                      Principal user)
708     throws ServletException JavaDoc
709   {
710     logout(application, null, sessionId, user);
711   }
712
713   static class PrincipalEntry {
714     private Principal _principal;
715     private ArrayList JavaDoc<SoftReference JavaDoc<SessionImpl>> _sessions;
716
717     PrincipalEntry(Principal principal)
718     {
719       _principal = principal;
720     }
721
722     Principal getPrincipal()
723     {
724       return _principal;
725     }
726
727     void addSession(SessionImpl session)
728     {
729       if (_sessions == null)
730     _sessions = new ArrayList JavaDoc<SoftReference JavaDoc<SessionImpl>>();
731       
732       _sessions.add(new SoftReference JavaDoc<SessionImpl>(session));
733     }
734
735     /**
736      * Logout only the given session, returning true if it's the
737      * last session to logout.
738      */

739     boolean logout(HttpSession JavaDoc timeoutSession)
740     {
741       ArrayList JavaDoc<SoftReference JavaDoc<SessionImpl>> sessions = _sessions;
742
743       if (sessions == null)
744     return true;
745
746       boolean isEmpty = true;
747       for (int i = sessions.size() - 1; i >= 0; i--) {
748     SoftReference JavaDoc<SessionImpl> ref = sessions.get(i);
749     SessionImpl session = ref.get();
750
751     try {
752       if (session == timeoutSession) {
753         sessions.remove(i);
754         session.logout();
755       }
756       else if (session == null)
757         sessions.remove(i);
758       else
759         isEmpty = false;
760     } catch (Throwable JavaDoc e) {
761       log.log(Level.WARNING, e.toString(), e);
762     }
763       }
764
765       return isEmpty;
766     }
767       
768     void logout()
769     {
770       ArrayList JavaDoc<SoftReference JavaDoc<SessionImpl>> sessions = _sessions;
771       _sessions = null;
772       
773       for (int i = 0; sessions != null && i < sessions.size(); i++) {
774     SoftReference JavaDoc<SessionImpl> ref = sessions.get(i);
775     SessionImpl session = ref.get();
776
777     try {
778       if (session != null) {
779         session.logout();
780         session.invalidate(); // #599, server/12i3
781
}
782     } catch (Throwable JavaDoc e) {
783       log.log(Level.WARNING, e.toString(), e);
784     }
785       }
786     }
787   }
788 }
789
Popular Tags