Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » authenticator » [javadoc | source]
    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   
   19   package org.apache.catalina.authenticator;
   20   
   21   
   22   import java.io.IOException;
   23   import java.security.Principal;
   24   import java.util.HashMap;
   25   import java.util.Map;
   26   
   27   import javax.servlet.ServletException;
   28   import javax.servlet.http.Cookie;
   29   
   30   import org.apache.catalina.Lifecycle;
   31   import org.apache.catalina.LifecycleException;
   32   import org.apache.catalina.LifecycleListener;
   33   import org.apache.catalina.Realm;
   34   import org.apache.catalina.Session;
   35   import org.apache.catalina.SessionEvent;
   36   import org.apache.catalina.SessionListener;
   37   import org.apache.catalina.connector.Request;
   38   import org.apache.catalina.connector.Response;
   39   import org.apache.catalina.util.LifecycleSupport;
   40   import org.apache.catalina.util.StringManager;
   41   import org.apache.catalina.valves.ValveBase;
   42   
   43   
   44   /**
   45    * A <strong>Valve</strong> that supports a "single sign on" user experience,
   46    * where the security identity of a user who successfully authenticates to one
   47    * web application is propogated to other web applications in the same
   48    * security domain.  For successful use, the following requirements must
   49    * be met:
   50    * <ul>
   51    * <li>This Valve must be configured on the Container that represents a
   52    *     virtual host (typically an implementation of <code>Host</code>).</li>
   53    * <li>The <code>Realm</code> that contains the shared user and role
   54    *     information must be configured on the same Container (or a higher
   55    *     one), and not overridden at the web application level.</li>
   56    * <li>The web applications themselves must use one of the standard
   57    *     Authenticators found in the
   58    *     <code>org.apache.catalina.authenticator</code> package.</li>
   59    * </ul>
   60    *
   61    * @author Craig R. McClanahan
   62    * @version $Revision: 536380 $ $Date: 2007-05-09 01:49:56 +0200 (mer., 09 mai 2007) $
   63    */
   64   
   65   public class SingleSignOn
   66       extends ValveBase
   67       implements Lifecycle, SessionListener {
   68   
   69   
   70       // ----------------------------------------------------- Instance Variables
   71   
   72   
   73       /**
   74        * The cache of SingleSignOnEntry instances for authenticated Principals,
   75        * keyed by the cookie value that is used to select them.
   76        */
   77       protected Map<String,SingleSignOnEntry> cache =
   78           new HashMap<String,SingleSignOnEntry>();
   79   
   80   
   81       /**
   82        * Descriptive information about this Valve implementation.
   83        */
   84       protected static String info =
   85           "org.apache.catalina.authenticator.SingleSignOn";
   86   
   87   
   88       /**
   89        * The lifecycle event support for this component.
   90        */
   91       protected LifecycleSupport lifecycle = new LifecycleSupport(this);
   92   
   93       /**
   94        * Indicates whether this valve should require a downstream Authenticator to
   95        * reauthenticate each request, or if it itself can bind a UserPrincipal
   96        * and AuthType object to the request.
   97        */
   98       private boolean requireReauthentication = false;
   99   
  100       /**
  101        * The cache of single sign on identifiers, keyed by the Session that is
  102        * associated with them.
  103        */
  104       protected Map<Session,String> reverse = new HashMap<Session,String>();
  105   
  106   
  107       /**
  108        * The string manager for this package.
  109        */
  110       protected final static StringManager sm =
  111           StringManager.getManager(Constants.Package);
  112   
  113   
  114       /**
  115        * Component started flag.
  116        */
  117       protected boolean started = false;
  118   
  119       /**
  120        * Optional SSO cookie domain.
  121        */
  122       private String cookieDomain;
  123   
  124       // ------------------------------------------------------------- Properties
  125   
  126       /**
  127        * Returns the optional cookie domain.
  128        * May return null.
  129        *
  130        * @return The cookie domain
  131        */
  132       public String getCookieDomain() {
  133           return cookieDomain;
  134       }
  135       /**
  136        * Sets the domain to be used for sso cookies.
  137        *
  138        * @param cookieDomain cookie domain name
  139        */
  140       public void setCookieDomain(String cookieDomain) {
  141           if (cookieDomain != null && cookieDomain.trim().length() == 0) {
  142               cookieDomain = null;
  143           }
  144           this.cookieDomain = cookieDomain;
  145       }
  146   
  147       /**
  148        * Gets whether each request needs to be reauthenticated (by an
  149        * Authenticator downstream in the pipeline) to the security
  150        * <code>Realm</code>, or if this Valve can itself bind security info
  151        * to the request based on the presence of a valid SSO entry without
  152        * rechecking with the <code>Realm</code..
  153        *
  154        * @return  <code>true</code> if it is required that a downstream
  155        *          Authenticator reauthenticate each request before calls to
  156        *          <code>HttpServletRequest.setUserPrincipal()</code>
  157        *          and <code>HttpServletRequest.setAuthType()</code> are made;
  158        *          <code>false</code> if the <code>Valve</code> can itself make
  159        *          those calls relying on the presence of a valid SingleSignOn
  160        *          entry associated with the request.
  161        *
  162        * @see #setRequireReauthentication
  163        */
  164       public boolean getRequireReauthentication()
  165       {
  166           return requireReauthentication;
  167       }
  168   
  169   
  170       /**
  171        * Sets whether each request needs to be reauthenticated (by an
  172        * Authenticator downstream in the pipeline) to the security
  173        * <code>Realm</code>, or if this Valve can itself bind security info
  174        * to the request, based on the presence of a valid SSO entry, without
  175        * rechecking with the <code>Realm</code.
  176        * <p>
  177        * If this property is <code>false</code> (the default), this
  178        * <code>Valve</code> will bind a UserPrincipal and AuthType to the request
  179        * if a valid SSO entry is associated with the request.  It will not notify
  180        * the security <code>Realm</code> of the incoming request.
  181        * <p>
  182        * This property should be set to <code>true</code> if the overall server
  183        * configuration requires that the <code>Realm</code> reauthenticate each
  184        * request thread.  An example of such a configuration would be one where
  185        * the <code>Realm</code> implementation provides security for both a
  186        * web tier and an associated EJB tier, and needs to set security
  187        * credentials on each request thread in order to support EJB access.
  188        * <p>
  189        * If this property is set to <code>true</code>, this Valve will set flags
  190        * on the request notifying the downstream Authenticator that the request
  191        * is associated with an SSO session.  The Authenticator will then call its
  192        * {@link AuthenticatorBase#reauthenticateFromSSO reauthenticateFromSSO}
  193        * method to attempt to reauthenticate the request to the
  194        * <code>Realm</code>, using any credentials that were cached with this
  195        * Valve.
  196        * <p>
  197        * The default value of this property is <code>false</code>, in order
  198        * to maintain backward compatibility with previous versions of Tomcat.
  199        *
  200        * @param required  <code>true</code> if it is required that a downstream
  201        *                  Authenticator reauthenticate each request before calls
  202        *                  to  <code>HttpServletRequest.setUserPrincipal()</code>
  203        *                  and <code>HttpServletRequest.setAuthType()</code> are
  204        *                  made; <code>false</code> if the <code>Valve</code> can
  205        *                  itself make those calls relying on the presence of a
  206        *                  valid SingleSignOn entry associated with the request.
  207        *
  208        * @see AuthenticatorBase#reauthenticateFromSSO
  209        */
  210       public void setRequireReauthentication(boolean required)
  211       {
  212           this.requireReauthentication = required;
  213       }
  214   
  215   
  216       // ------------------------------------------------------ Lifecycle Methods
  217   
  218   
  219       /**
  220        * Add a lifecycle event listener to this component.
  221        *
  222        * @param listener The listener to add
  223        */
  224       public void addLifecycleListener(LifecycleListener listener) {
  225   
  226           lifecycle.addLifecycleListener(listener);
  227   
  228       }
  229   
  230   
  231       /**
  232        * Get the lifecycle listeners associated with this lifecycle. If this 
  233        * Lifecycle has no listeners registered, a zero-length array is returned.
  234        */
  235       public LifecycleListener[] findLifecycleListeners() {
  236   
  237           return lifecycle.findLifecycleListeners();
  238   
  239       }
  240   
  241   
  242       /**
  243        * Remove a lifecycle event listener from this component.
  244        *
  245        * @param listener The listener to remove
  246        */
  247       public void removeLifecycleListener(LifecycleListener listener) {
  248   
  249           lifecycle.removeLifecycleListener(listener);
  250   
  251       }
  252   
  253   
  254       /**
  255        * Prepare for the beginning of active use of the public methods of this
  256        * component.  This method should be called after <code>configure()</code>,
  257        * and before any of the public methods of the component are utilized.
  258        *
  259        * @exception LifecycleException if this component detects a fatal error
  260        *  that prevents this component from being used
  261        */
  262       public void start() throws LifecycleException {
  263   
  264           // Validate and update our current component state
  265           if (started)
  266               throw new LifecycleException
  267                   (sm.getString("authenticator.alreadyStarted"));
  268           lifecycle.fireLifecycleEvent(START_EVENT, null);
  269           started = true;
  270   
  271       }
  272   
  273   
  274       /**
  275        * Gracefully terminate the active use of the public methods of this
  276        * component.  This method should be the last one called on a given
  277        * instance of this component.
  278        *
  279        * @exception LifecycleException if this component detects a fatal error
  280        *  that needs to be reported
  281        */
  282       public void stop() throws LifecycleException {
  283   
  284           // Validate and update our current component state
  285           if (!started)
  286               throw new LifecycleException
  287                   (sm.getString("authenticator.notStarted"));
  288           lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  289           started = false;
  290   
  291       }
  292   
  293   
  294       // ------------------------------------------------ SessionListener Methods
  295   
  296   
  297       /**
  298        * Acknowledge the occurrence of the specified event.
  299        *
  300        * @param event SessionEvent that has occurred
  301        */
  302       public void sessionEvent(SessionEvent event) {
  303   
  304           // We only care about session destroyed events
  305           if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
  306                   && (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType())))
  307               return;
  308   
  309           // Look up the single session id associated with this session (if any)
  310           Session session = event.getSession();
  311           if (containerLog.isDebugEnabled())
  312               containerLog.debug("Process session destroyed on " + session);
  313   
  314           String ssoId = null;
  315           synchronized (reverse) {
  316               ssoId = (String) reverse.get(session);
  317           }
  318           if (ssoId == null)
  319               return;
  320   
  321           // Was the session destroyed as the result of a timeout?
  322           // If so, we'll just remove the expired session from the
  323           // SSO.  If the session was logged out, we'll log out
  324           // of all session associated with the SSO.
  325           if (((session.getMaxInactiveInterval() > 0)
  326               && (System.currentTimeMillis() - session.getLastAccessedTimeInternal() >=
  327                   session.getMaxInactiveInterval() * 1000)) 
  328               || (Session.SESSION_PASSIVATED_EVENT.equals(event.getType()))) {
  329               removeSession(ssoId, session);
  330           } else {
  331               // The session was logged out.
  332               // Deregister this single session id, invalidating 
  333               // associated sessions
  334               deregister(ssoId);
  335           }
  336   
  337       }
  338   
  339   
  340       // ---------------------------------------------------------- Valve Methods
  341   
  342   
  343       /**
  344        * Return descriptive information about this Valve implementation.
  345        */
  346       public String getInfo() {
  347   
  348           return (info);
  349   
  350       }
  351   
  352   
  353       /**
  354        * Perform single-sign-on support processing for this request.
  355        *
  356        * @param request The servlet request we are processing
  357        * @param response The servlet response we are creating
  358        *
  359        * @exception IOException if an input/output error occurs
  360        * @exception ServletException if a servlet error occurs
  361        */
  362       public void invoke(Request request, Response response)
  363           throws IOException, ServletException {
  364   
  365           request.removeNote(Constants.REQ_SSOID_NOTE);
  366   
  367           // Has a valid user already been authenticated?
  368           if (containerLog.isDebugEnabled())
  369               containerLog.debug("Process request for '" + request.getRequestURI() + "'");
  370           if (request.getUserPrincipal() != null) {
  371               if (containerLog.isDebugEnabled())
  372                   containerLog.debug(" Principal '" + request.getUserPrincipal().getName() +
  373                       "' has already been authenticated");
  374               getNext().invoke(request, response);
  375               return;
  376           }
  377   
  378           // Check for the single sign on cookie
  379           if (containerLog.isDebugEnabled())
  380               containerLog.debug(" Checking for SSO cookie");
  381           Cookie cookie = null;
  382           Cookie cookies[] = request.getCookies();
  383           if (cookies == null)
  384               cookies = new Cookie[0];
  385           for (int i = 0; i < cookies.length; i++) {
  386               if (Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())) {
  387                   cookie = cookies[i];
  388                   break;
  389               }
  390           }
  391           if (cookie == null) {
  392               if (containerLog.isDebugEnabled())
  393                   containerLog.debug(" SSO cookie is not present");
  394               getNext().invoke(request, response);
  395               return;
  396           }
  397   
  398           // Look up the cached Principal associated with this cookie value
  399           if (containerLog.isDebugEnabled())
  400               containerLog.debug(" Checking for cached principal for " + cookie.getValue());
  401           SingleSignOnEntry entry = lookup(cookie.getValue());
  402           if (entry != null) {
  403               if (containerLog.isDebugEnabled())
  404                   containerLog.debug(" Found cached principal '" +
  405                       (entry.getPrincipal() != null ? entry.getPrincipal().getName() : "") + "' with auth type '" +
  406                       entry.getAuthType() + "'");
  407               request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
  408               // Only set security elements if reauthentication is not required
  409               if (!getRequireReauthentication()) {
  410                   request.setAuthType(entry.getAuthType());
  411                   request.setUserPrincipal(entry.getPrincipal());
  412               }
  413           } else {
  414               if (containerLog.isDebugEnabled())
  415                   containerLog.debug(" No cached principal found, erasing SSO cookie");
  416               cookie.setMaxAge(0);
  417               response.addCookie(cookie);
  418           }
  419   
  420           // Invoke the next Valve in our pipeline
  421           getNext().invoke(request, response);
  422   
  423       }
  424   
  425   
  426       // --------------------------------------------------------- Public Methods
  427   
  428   
  429       /**
  430        * Return a String rendering of this object.
  431        */
  432       public String toString() {
  433   
  434           StringBuffer sb = new StringBuffer("SingleSignOn[");
  435           if (container == null )
  436               sb.append("Container is null");
  437           else
  438               sb.append(container.getName());
  439           sb.append("]");
  440           return (sb.toString());
  441   
  442       }
  443   
  444   
  445       // ------------------------------------------------------ Protected Methods
  446   
  447   
  448       /**
  449        * Associate the specified single sign on identifier with the
  450        * specified Session.
  451        *
  452        * @param ssoId Single sign on identifier
  453        * @param session Session to be associated
  454        */
  455       protected void associate(String ssoId, Session session) {
  456   
  457           if (containerLog.isDebugEnabled())
  458               containerLog.debug("Associate sso id " + ssoId + " with session " + session);
  459   
  460           SingleSignOnEntry sso = lookup(ssoId);
  461           if (sso != null)
  462               sso.addSession(this, session);
  463           synchronized (reverse) {
  464               reverse.put(session, ssoId);
  465           }
  466   
  467       }
  468   
  469       /**
  470        * Deregister the specified session.  If it is the last session,
  471        * then also get rid of the single sign on identifier
  472        *
  473        * @param ssoId Single sign on identifier
  474        * @param session Session to be deregistered
  475        */
  476       protected void deregister(String ssoId, Session session) {
  477   
  478           synchronized (reverse) {
  479               reverse.remove(session);
  480           }
  481   
  482           SingleSignOnEntry sso = lookup(ssoId);
  483           if ( sso == null )
  484               return;
  485   
  486           sso.removeSession( session );
  487   
  488           // see if we are the last session, if so blow away ssoId
  489           Session sessions[] = sso.findSessions();
  490           if ( sessions == null || sessions.length == 0 ) {
  491               synchronized (cache) {
  492                   sso = (SingleSignOnEntry) cache.remove(ssoId);
  493               }
  494           }
  495   
  496       }
  497   
  498   
  499       /**
  500        * Deregister the specified single sign on identifier, and invalidate
  501        * any associated sessions.
  502        *
  503        * @param ssoId Single sign on identifier to deregister
  504        */
  505       protected void deregister(String ssoId) {
  506   
  507           if (containerLog.isDebugEnabled())
  508               containerLog.debug("Deregistering sso id '" + ssoId + "'");
  509   
  510           // Look up and remove the corresponding SingleSignOnEntry
  511           SingleSignOnEntry sso = null;
  512           synchronized (cache) {
  513               sso = (SingleSignOnEntry) cache.remove(ssoId);
  514           }
  515   
  516           if (sso == null)
  517               return;
  518   
  519           // Expire any associated sessions
  520           Session sessions[] = sso.findSessions();
  521           for (int i = 0; i < sessions.length; i++) {
  522               if (containerLog.isTraceEnabled())
  523                   containerLog.trace(" Invalidating session " + sessions[i]);
  524               // Remove from reverse cache first to avoid recursion
  525               synchronized (reverse) {
  526                   reverse.remove(sessions[i]);
  527               }
  528               // Invalidate this session
  529               sessions[i].expire();
  530           }
  531   
  532           // NOTE:  Clients may still possess the old single sign on cookie,
  533           // but it will be removed on the next request since it is no longer
  534           // in the cache
  535   
  536       }
  537   
  538   
  539       /**
  540        * Attempts reauthentication to the given <code>Realm</code> using
  541        * the credentials associated with the single sign-on session
  542        * identified by argument <code>ssoId</code>.
  543        * <p>
  544        * If reauthentication is successful, the <code>Principal</code> and
  545        * authorization type associated with the SSO session will be bound
  546        * to the given <code>Request</code> object via calls to 
  547        * {@link Request#setAuthType Request.setAuthType()} and 
  548        * {@link Request#setUserPrincipal Request.setUserPrincipal()}
  549        * </p>
  550        *
  551        * @param ssoId     identifier of SingleSignOn session with which the
  552        *                  caller is associated
  553        * @param realm     Realm implementation against which the caller is to
  554        *                  be authenticated
  555        * @param request   the request that needs to be authenticated
  556        * 
  557        * @return  <code>true</code> if reauthentication was successful,
  558        *          <code>false</code> otherwise.
  559        */
  560       protected boolean reauthenticate(String ssoId, Realm realm,
  561                                        Request request) {
  562   
  563           if (ssoId == null || realm == null)
  564               return false;
  565   
  566           boolean reauthenticated = false;
  567   
  568           SingleSignOnEntry entry = lookup(ssoId);
  569           if (entry != null && entry.getCanReauthenticate()) {
  570               
  571               String username = entry.getUsername();
  572               if (username != null) {
  573                   Principal reauthPrincipal =
  574                           realm.authenticate(username, entry.getPassword());                
  575                   if (reauthPrincipal != null) {                    
  576                       reauthenticated = true;                    
  577                       // Bind the authorization credentials to the request
  578                       request.setAuthType(entry.getAuthType());
  579                       request.setUserPrincipal(reauthPrincipal);
  580                   }
  581               }
  582           }
  583   
  584           return reauthenticated;
  585       }
  586   
  587   
  588       /**
  589        * Register the specified Principal as being associated with the specified
  590        * value for the single sign on identifier.
  591        *
  592        * @param ssoId Single sign on identifier to register
  593        * @param principal Associated user principal that is identified
  594        * @param authType Authentication type used to authenticate this
  595        *  user principal
  596        * @param username Username used to authenticate this user
  597        * @param password Password used to authenticate this user
  598        */
  599       protected void register(String ssoId, Principal principal, String authType,
  600                     String username, String password) {
  601   
  602           if (containerLog.isDebugEnabled())
  603               containerLog.debug("Registering sso id '" + ssoId + "' for user '" +
  604                   (principal != null ? principal.getName() : "") + "' with auth type '" + authType + "'");
  605   
  606           synchronized (cache) {
  607               cache.put(ssoId, new SingleSignOnEntry(principal, authType,
  608                                                      username, password));
  609           }
  610   
  611       }
  612   
  613   
  614       /**
  615        * Updates any <code>SingleSignOnEntry</code> found under key
  616        * <code>ssoId</code> with the given authentication data.
  617        * <p>
  618        * The purpose of this method is to allow an SSO entry that was
  619        * established without a username/password combination (i.e. established
  620        * following DIGEST or CLIENT_CERT authentication) to be updated with
  621        * a username and password if one becomes available through a subsequent
  622        * BASIC or FORM authentication.  The SSO entry will then be usable for
  623        * reauthentication.
  624        * <p>
  625        * <b>NOTE:</b> Only updates the SSO entry if a call to
  626        * <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
  627        * <code>false</code>; otherwise, it is assumed that the SSO entry already
  628        * has sufficient information to allow reauthentication and that no update
  629        * is needed.
  630        *
  631        * @param ssoId     identifier of Single sign to be updated
  632        * @param principal the <code>Principal</code> returned by the latest
  633        *                  call to <code>Realm.authenticate</code>.
  634        * @param authType  the type of authenticator used (BASIC, CLIENT_CERT,
  635        *                  DIGEST or FORM)
  636        * @param username  the username (if any) used for the authentication
  637        * @param password  the password (if any) used for the authentication
  638        */
  639       protected void update(String ssoId, Principal principal, String authType,
  640                             String username, String password) {
  641   
  642           SingleSignOnEntry sso = lookup(ssoId);
  643           if (sso != null && !sso.getCanReauthenticate()) {
  644               if (containerLog.isDebugEnabled())
  645                   containerLog.debug("Update sso id " + ssoId + " to auth type " + authType);
  646   
  647               synchronized(sso) {
  648                   sso.updateCredentials(principal, authType, username, password);
  649               }
  650   
  651           }
  652       }
  653   
  654   
  655       /**
  656        * Look up and return the cached SingleSignOn entry associated with this
  657        * sso id value, if there is one; otherwise return <code>null</code>.
  658        *
  659        * @param ssoId Single sign on identifier to look up
  660        */
  661       protected SingleSignOnEntry lookup(String ssoId) {
  662   
  663           synchronized (cache) {
  664               return ((SingleSignOnEntry) cache.get(ssoId));
  665           }
  666   
  667       }
  668   
  669       
  670       /**
  671        * Remove a single Session from a SingleSignOn.  Called when
  672        * a session is timed out and no longer active.
  673        *
  674        * @param ssoId Single sign on identifier from which to remove the session.
  675        * @param session the session to be removed.
  676        */
  677       protected void removeSession(String ssoId, Session session) {
  678   
  679           if (containerLog.isDebugEnabled())
  680               containerLog.debug("Removing session " + session.toString() + " from sso id " + 
  681                   ssoId );
  682   
  683           // Get a reference to the SingleSignOn
  684           SingleSignOnEntry entry = lookup(ssoId);
  685           if (entry == null)
  686               return;
  687   
  688           // Remove the inactive session from SingleSignOnEntry
  689           entry.removeSession(session);
  690   
  691           // Remove the inactive session from the 'reverse' Map.
  692           synchronized(reverse) {
  693               reverse.remove(session);
  694           }
  695   
  696           // If there are not sessions left in the SingleSignOnEntry,
  697           // deregister the entry.
  698           if (entry.findSessions().length == 0) {
  699               deregister(ssoId);
  700           }
  701       }
  702   
  703   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » authenticator » [javadoc | source]