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.io.InputStream;
   24   import java.security.Principal;
   25   import java.util.Enumeration;
   26   import java.util.Iterator;
   27   import java.util.Locale;
   28   
   29   import javax.servlet.RequestDispatcher;
   30   import javax.servlet.http.Cookie;
   31   import javax.servlet.http.HttpServletResponse;
   32   
   33   import org.apache.catalina.Realm;
   34   import org.apache.catalina.Session;
   35   import org.apache.catalina.connector.Request;
   36   import org.apache.catalina.connector.Response;
   37   import org.apache.catalina.deploy.LoginConfig;
   38   import org.apache.coyote.ActionCode;
   39   import org.apache.juli.logging.Log;
   40   import org.apache.juli.logging.LogFactory;
   41   import org.apache.tomcat.util.buf.ByteChunk;
   42   import org.apache.tomcat.util.buf.CharChunk;
   43   import org.apache.tomcat.util.buf.MessageBytes;
   44   import org.apache.tomcat.util.http.MimeHeaders;
   45   
   46   
   47   /**
   48    * An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED
   49    * Authentication, as described in the Servlet API Specification, Version 2.2.
   50    *
   51    * @author Craig R. McClanahan
   52    * @author Remy Maucherat
   53    * @version $Revision: 599259 $ $Date: 2007-11-29 05:19:46 +0100 (jeu., 29 nov. 2007) $
   54    */
   55   
   56   public class FormAuthenticator
   57       extends AuthenticatorBase {
   58       
   59       private static Log log = LogFactory.getLog(FormAuthenticator.class);
   60   
   61       // ----------------------------------------------------- Instance Variables
   62   
   63   
   64       /**
   65        * Descriptive information about this implementation.
   66        */
   67       protected static final String info =
   68           "org.apache.catalina.authenticator.FormAuthenticator/1.0";
   69   
   70       /**
   71        * Character encoding to use to read the username and password parameters
   72        * from the request. If not set, the encoding of the request body will be
   73        * used.
   74        */
   75       protected String characterEncoding = null;
   76   
   77   
   78       // ------------------------------------------------------------- Properties
   79   
   80   
   81       /**
   82        * Return descriptive information about this Valve implementation.
   83        */
   84       public String getInfo() {
   85   
   86           return (info);
   87   
   88       }
   89   
   90   
   91       /**
   92        * Return the character encoding to use to read the username and password.
   93        */
   94       public String getCharacterEncoding() {
   95           return characterEncoding;
   96       }
   97   
   98       
   99       /**
  100        * Set the character encoding to be used to read the username and password. 
  101        */
  102       public void setCharacterEncoding(String encoding) {
  103           characterEncoding = encoding;
  104       }
  105   
  106   
  107       // --------------------------------------------------------- Public Methods
  108   
  109   
  110       /**
  111        * Authenticate the user making this request, based on the specified
  112        * login configuration.  Return <code>true</code> if any specified
  113        * constraint has been satisfied, or <code>false</code> if we have
  114        * created a response challenge already.
  115        *
  116        * @param request Request we are processing
  117        * @param response Response we are creating
  118        * @param config    Login configuration describing how authentication
  119        *              should be performed
  120        *
  121        * @exception IOException if an input/output error occurs
  122        */
  123       public boolean authenticate(Request request,
  124                                   Response response,
  125                                   LoginConfig config)
  126           throws IOException {
  127   
  128           // References to objects we will need later
  129           Session session = null;
  130   
  131           // Have we already authenticated someone?
  132           Principal principal = request.getUserPrincipal();
  133           String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
  134           if (principal != null) {
  135               if (log.isDebugEnabled())
  136                   log.debug("Already authenticated '" +
  137                       principal.getName() + "'");
  138               // Associate the session with any existing SSO session
  139               if (ssoId != null)
  140                   associate(ssoId, request.getSessionInternal(true));
  141               return (true);
  142           }
  143   
  144           // Is there an SSO session against which we can try to reauthenticate?
  145           if (ssoId != null) {
  146               if (log.isDebugEnabled())
  147                   log.debug("SSO Id " + ssoId + " set; attempting " +
  148                             "reauthentication");
  149               // Try to reauthenticate using data cached by SSO.  If this fails,
  150               // either the original SSO logon was of DIGEST or SSL (which
  151               // we can't reauthenticate ourselves because there is no
  152               // cached username and password), or the realm denied
  153               // the user's reauthentication for some reason.
  154               // In either case we have to prompt the user for a logon */
  155               if (reauthenticateFromSSO(ssoId, request))
  156                   return true;
  157           }
  158   
  159           // Have we authenticated this user before but have caching disabled?
  160           if (!cache) {
  161               session = request.getSessionInternal(true);
  162               if (log.isDebugEnabled())
  163                   log.debug("Checking for reauthenticate in session " + session);
  164               String username =
  165                   (String) session.getNote(Constants.SESS_USERNAME_NOTE);
  166               String password =
  167                   (String) session.getNote(Constants.SESS_PASSWORD_NOTE);
  168               if ((username != null) && (password != null)) {
  169                   if (log.isDebugEnabled())
  170                       log.debug("Reauthenticating username '" + username + "'");
  171                   principal =
  172                       context.getRealm().authenticate(username, password);
  173                   if (principal != null) {
  174                       session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
  175                       if (!matchRequest(request)) {
  176                           register(request, response, principal,
  177                                    Constants.FORM_METHOD,
  178                                    username, password);
  179                           return (true);
  180                       }
  181                   }
  182                   if (log.isDebugEnabled())
  183                       log.debug("Reauthentication failed, proceed normally");
  184               }
  185           }
  186   
  187           // Is this the re-submit of the original request URI after successful
  188           // authentication?  If so, forward the *original* request instead.
  189           if (matchRequest(request)) {
  190               session = request.getSessionInternal(true);
  191               if (log.isDebugEnabled())
  192                   log.debug("Restore request from session '"
  193                             + session.getIdInternal() 
  194                             + "'");
  195               principal = (Principal)
  196                   session.getNote(Constants.FORM_PRINCIPAL_NOTE);
  197               register(request, response, principal, Constants.FORM_METHOD,
  198                        (String) session.getNote(Constants.SESS_USERNAME_NOTE),
  199                        (String) session.getNote(Constants.SESS_PASSWORD_NOTE));
  200               // If we're caching principals we no longer need the username
  201               // and password in the session, so remove them
  202               if (cache) {
  203                   session.removeNote(Constants.SESS_USERNAME_NOTE);
  204                   session.removeNote(Constants.SESS_PASSWORD_NOTE);
  205               }
  206               if (restoreRequest(request, session)) {
  207                   if (log.isDebugEnabled())
  208                       log.debug("Proceed to restored request");
  209                   return (true);
  210               } else {
  211                   if (log.isDebugEnabled())
  212                       log.debug("Restore of original request failed");
  213                   response.sendError(HttpServletResponse.SC_BAD_REQUEST);
  214                   return (false);
  215               }
  216           }
  217   
  218           // Acquire references to objects we will need to evaluate
  219           MessageBytes uriMB = MessageBytes.newInstance();
  220           CharChunk uriCC = uriMB.getCharChunk();
  221           uriCC.setLimit(-1);
  222           String contextPath = request.getContextPath();
  223           String requestURI = request.getDecodedRequestURI();
  224           response.setContext(request.getContext());
  225   
  226           // Is this the action request from the login page?
  227           boolean loginAction =
  228               requestURI.startsWith(contextPath) &&
  229               requestURI.endsWith(Constants.FORM_ACTION);
  230   
  231           // No -- Save this request and redirect to the form login page
  232           if (!loginAction) {
  233               session = request.getSessionInternal(true);
  234               if (log.isDebugEnabled())
  235                   log.debug("Save request in session '" + session.getIdInternal() + "'");
  236               try {
  237                   saveRequest(request, session);
  238               } catch (IOException ioe) {
  239                   log.debug("Request body too big to save during authentication");
  240                   response.sendError(HttpServletResponse.SC_FORBIDDEN,
  241                           sm.getString("authenticator.requestBodyTooBig"));
  242                   return (false);
  243               }
  244               forwardToLoginPage(request, response, config);
  245               return (false);
  246           }
  247   
  248           // Yes -- Validate the specified credentials and redirect
  249           // to the error page if they are not correct
  250           Realm realm = context.getRealm();
  251           if (characterEncoding != null) {
  252               request.setCharacterEncoding(characterEncoding);
  253           }
  254           String username = request.getParameter(Constants.FORM_USERNAME);
  255           String password = request.getParameter(Constants.FORM_PASSWORD);
  256           if (log.isDebugEnabled())
  257               log.debug("Authenticating username '" + username + "'");
  258           principal = realm.authenticate(username, password);
  259           if (principal == null) {
  260               forwardToErrorPage(request, response, config);
  261               return (false);
  262           }
  263   
  264           if (log.isDebugEnabled())
  265               log.debug("Authentication of '" + username + "' was successful");
  266   
  267           if (session == null)
  268               session = request.getSessionInternal(false);
  269           if (session == null) {
  270               if (containerLog.isDebugEnabled())
  271                   containerLog.debug
  272                       ("User took so long to log on the session expired");
  273               response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT,
  274                                  sm.getString("authenticator.sessionExpired"));
  275               return (false);
  276           }
  277   
  278           // Save the authenticated Principal in our session
  279           session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
  280   
  281           // Save the username and password as well
  282           session.setNote(Constants.SESS_USERNAME_NOTE, username);
  283           session.setNote(Constants.SESS_PASSWORD_NOTE, password);
  284   
  285           // Redirect the user to the original request URI (which will cause
  286           // the original request to be restored)
  287           requestURI = savedRequestURL(session);
  288           if (log.isDebugEnabled())
  289               log.debug("Redirecting to original '" + requestURI + "'");
  290           if (requestURI == null)
  291               response.sendError(HttpServletResponse.SC_BAD_REQUEST,
  292                                  sm.getString("authenticator.formlogin"));
  293           else
  294               response.sendRedirect(response.encodeRedirectURL(requestURI));
  295           return (false);
  296   
  297       }
  298   
  299   
  300       // ------------------------------------------------------ Protected Methods
  301   
  302   
  303       /**
  304        * Called to forward to the login page
  305        * 
  306        * @param request Request we are processing
  307        * @param response Response we are creating
  308        * @param config    Login configuration describing how authentication
  309        *              should be performed
  310        */
  311       protected void forwardToLoginPage(Request request, Response response, LoginConfig config) {
  312           RequestDispatcher disp =
  313               context.getServletContext().getRequestDispatcher
  314               (config.getLoginPage());
  315           try {
  316               disp.forward(request.getRequest(), response.getResponse());
  317               response.finishResponse();
  318           } catch (Throwable t) {
  319               log.warn("Unexpected error forwarding to login page", t);
  320           }
  321       }
  322   
  323   
  324       /**
  325        * Called to forward to the error page
  326        * 
  327        * @param request Request we are processing
  328        * @param response Response we are creating
  329        * @param config    Login configuration describing how authentication
  330        *              should be performed
  331        */
  332       protected void forwardToErrorPage(Request request, Response response, LoginConfig config) {
  333           RequestDispatcher disp =
  334               context.getServletContext().getRequestDispatcher
  335               (config.getErrorPage());
  336           try {
  337               disp.forward(request.getRequest(), response.getResponse());
  338           } catch (Throwable t) {
  339               log.warn("Unexpected error forwarding to error page", t);
  340           }
  341       }
  342   
  343   
  344       /**
  345        * Does this request match the saved one (so that it must be the redirect
  346        * we signalled after successful authentication?
  347        *
  348        * @param request The request to be verified
  349        */
  350       protected boolean matchRequest(Request request) {
  351   
  352         // Has a session been created?
  353         Session session = request.getSessionInternal(false);
  354         if (session == null)
  355             return (false);
  356   
  357         // Is there a saved request?
  358         SavedRequest sreq = (SavedRequest)
  359             session.getNote(Constants.FORM_REQUEST_NOTE);
  360         if (sreq == null)
  361             return (false);
  362   
  363         // Is there a saved principal?
  364         if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null)
  365             return (false);
  366   
  367         // Does the request URI match?
  368         String requestURI = request.getRequestURI();
  369         if (requestURI == null)
  370             return (false);
  371         return (requestURI.equals(sreq.getRequestURI()));
  372   
  373       }
  374   
  375   
  376       /**
  377        * Restore the original request from information stored in our session.
  378        * If the original request is no longer present (because the session
  379        * timed out), return <code>false</code>; otherwise, return
  380        * <code>true</code>.
  381        *
  382        * @param request The request to be restored
  383        * @param session The session containing the saved information
  384        */
  385       protected boolean restoreRequest(Request request, Session session)
  386           throws IOException {
  387   
  388           // Retrieve and remove the SavedRequest object from our session
  389           SavedRequest saved = (SavedRequest)
  390               session.getNote(Constants.FORM_REQUEST_NOTE);
  391           session.removeNote(Constants.FORM_REQUEST_NOTE);
  392           session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
  393           if (saved == null)
  394               return (false);
  395   
  396           // Modify our current request to reflect the original one
  397           request.clearCookies();
  398           Iterator cookies = saved.getCookies();
  399           while (cookies.hasNext()) {
  400               request.addCookie((Cookie) cookies.next());
  401           }
  402   
  403           MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders();
  404           rmh.recycle();
  405           boolean cachable = "GET".equalsIgnoreCase(saved.getMethod()) ||
  406                              "HEAD".equalsIgnoreCase(saved.getMethod());
  407           Iterator names = saved.getHeaderNames();
  408           while (names.hasNext()) {
  409               String name = (String) names.next();
  410               // The browser isn't expecting this conditional response now.
  411               // Assuming that it can quietly recover from an unexpected 412.
  412               // BZ 43687
  413               if(!("If-Modified-Since".equalsIgnoreCase(name) ||
  414                    (cachable && "If-None-Match".equalsIgnoreCase(name)))) {
  415                   Iterator values = saved.getHeaderValues(name);
  416                   while (values.hasNext()) {
  417                       rmh.addValue(name).setString( (String)values.next() );
  418                   }
  419               }
  420           }
  421           
  422           request.clearLocales();
  423           Iterator locales = saved.getLocales();
  424           while (locales.hasNext()) {
  425               request.addLocale((Locale) locales.next());
  426           }
  427           
  428           request.getCoyoteRequest().getParameters().recycle();
  429           
  430           if ("POST".equalsIgnoreCase(saved.getMethod())) {
  431               ByteChunk body = saved.getBody();
  432               
  433               if (body != null) {
  434                   request.getCoyoteRequest().action
  435                       (ActionCode.ACTION_REQ_SET_BODY_REPLAY, body);
  436       
  437                   // Set content type
  438                   MessageBytes contentType = MessageBytes.newInstance();
  439                   
  440                   //If no content type specified, use default for POST
  441                   String savedContentType = saved.getContentType();
  442                   if (savedContentType == null) {
  443                       savedContentType = "application/x-www-form-urlencoded";
  444                   }
  445   
  446                   contentType.setString(savedContentType);
  447                   request.getCoyoteRequest().setContentType(contentType);
  448               }
  449           }
  450           request.getCoyoteRequest().method().setString(saved.getMethod());
  451   
  452           request.getCoyoteRequest().queryString().setString
  453               (saved.getQueryString());
  454   
  455           request.getCoyoteRequest().requestURI().setString
  456               (saved.getRequestURI());
  457           return (true);
  458   
  459       }
  460   
  461   
  462       /**
  463        * Save the original request information into our session.
  464        *
  465        * @param request The request to be saved
  466        * @param session The session to contain the saved information
  467        * @throws IOException
  468        */
  469       protected void saveRequest(Request request, Session session)
  470           throws IOException {
  471   
  472           // Create and populate a SavedRequest object for this request
  473           SavedRequest saved = new SavedRequest();
  474           Cookie cookies[] = request.getCookies();
  475           if (cookies != null) {
  476               for (int i = 0; i < cookies.length; i++)
  477                   saved.addCookie(cookies[i]);
  478           }
  479           Enumeration names = request.getHeaderNames();
  480           while (names.hasMoreElements()) {
  481               String name = (String) names.nextElement();
  482               Enumeration values = request.getHeaders(name);
  483               while (values.hasMoreElements()) {
  484                   String value = (String) values.nextElement();
  485                   saved.addHeader(name, value);
  486               }
  487           }
  488           Enumeration locales = request.getLocales();
  489           while (locales.hasMoreElements()) {
  490               Locale locale = (Locale) locales.nextElement();
  491               saved.addLocale(locale);
  492           }
  493   
  494           if ("POST".equalsIgnoreCase(request.getMethod())) {
  495               ByteChunk body = new ByteChunk();
  496               body.setLimit(request.getConnector().getMaxSavePostSize());
  497   
  498               byte[] buffer = new byte[4096];
  499               int bytesRead;
  500               InputStream is = request.getInputStream();
  501           
  502               while ( (bytesRead = is.read(buffer) ) >= 0) {
  503                   body.append(buffer, 0, bytesRead);
  504               }
  505               saved.setContentType(request.getContentType());
  506               saved.setBody(body);
  507           }
  508   
  509           saved.setMethod(request.getMethod());
  510           saved.setQueryString(request.getQueryString());
  511           saved.setRequestURI(request.getRequestURI());
  512   
  513           // Stash the SavedRequest in our session for later use
  514           session.setNote(Constants.FORM_REQUEST_NOTE, saved);
  515   
  516       }
  517   
  518   
  519       /**
  520        * Return the request URI (with the corresponding query string, if any)
  521        * from the saved request so that we can redirect to it.
  522        *
  523        * @param session Our current session
  524        */
  525       protected String savedRequestURL(Session session) {
  526   
  527           SavedRequest saved =
  528               (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
  529           if (saved == null)
  530               return (null);
  531           StringBuffer sb = new StringBuffer(saved.getRequestURI());
  532           if (saved.getQueryString() != null) {
  533               sb.append('?');
  534               sb.append(saved.getQueryString());
  535           }
  536           return (sb.toString());
  537   
  538       }
  539   
  540   
  541   }

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