View Javadoc

    1   // ========================================================================
    2   // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.security;
    16
    17  import java.io.IOException;
    18  import java.io.Serializable;
    19  import java.security.Principal;
    20
    21  import javax.servlet.http.HttpServletRequest;
    22  import javax.servlet.http.HttpServletResponse;
    23  import javax.servlet.http.HttpSession;
    24  import javax.servlet.http.HttpSessionBindingEvent;
    25  import javax.servlet.http.HttpSessionBindingListener;
    26
    27  import org.mortbay.jetty.Request;
    28  import org.mortbay.jetty.Response;
    29  import org.mortbay.log.Log;
    30  import org.mortbay.util.StringUtil;
    31  import org.mortbay.util.URIUtil;
    32
    33
    34  /* ------------------------------------------------------------ */
    35  /** FORM Authentication Authenticator.
    36   * The HTTP Session is used to store the authentication status of the
    37   * user, which can be distributed.
    38   * If the realm implements SSORealm, SSO is supported.
    39   *
    40   * @author Greg Wilkins (gregw)
    41   * @author dan@greening.name
    42   */
    43  public class FormAuthenticator implements Authenticator
    44  {
    45      /* ------------------------------------------------------------ */
    46      public final static String __J_URI="org.mortbay.jetty.URI";
    47      public final static String __J_AUTHENTICATED="org.mortbay.jetty.Auth";
    48      public final static String __J_SECURITY_CHECK="/j_security_check";
    49      public final static String __J_USERNAME="j_username";
    50      public final static String __J_PASSWORD="j_password";
    51
    52      private String _formErrorPage;
    53      private String _formErrorPath;
    54      private String _formLoginPage;
    55      private String _formLoginPath;
    56
    57      /* ------------------------------------------------------------ */
    58      public String getAuthMethod()
    59      {
    60          return HttpServletRequest.FORM_AUTH;
    61      }
    62
    63      /* ------------------------------------------------------------ */
    64      public void setLoginPage(String path)
    65      {
    66          if (!path.startsWith("/"))
    67          {
    68              Log.warn("form-login-page must start with /");
    69              path="/"+path;
    70          }
    71          _formLoginPage=path;
    72          _formLoginPath=path;
    73          if (_formLoginPath.indexOf('?')>0)
    74              _formLoginPath=_formLoginPath.substring(0,_formLoginPath.indexOf('?'));
    75      }
    76
    77      /* ------------------------------------------------------------ */
    78      public String getLoginPage()
    79      {
    80          return _formLoginPage;
    81      }
    82
    83      /* ------------------------------------------------------------ */
    84      public void setErrorPage(String path)
    85      {
    86          if (path==null || path.trim().length()==0)
    87          {
    88              _formErrorPath=null;
    89              _formErrorPage=null;
    90          }
    91          else
    92          {
    93              if (!path.startsWith("/"))
    94              {
    95                  Log.warn("form-error-page must start with /");
    96                  path="/"+path;
    97              }
    98              _formErrorPage=path;
    99              _formErrorPath=path;
    100
    101             if (_formErrorPath!=null && _formErrorPath.indexOf('?')>0)
    102                 _formErrorPath=_formErrorPath.substring(0,_formErrorPath.indexOf('?'));
    103         }
    104     }
    105
    106     /* ------------------------------------------------------------ */
    107     public String getErrorPage()
    108     {
    109         return _formErrorPage;
    110     }
    111
    112     /* ------------------------------------------------------------ */
    113     /** Perform form authentication.
    114      * Called from SecurityHandler.
    115      * @return UserPrincipal if authenticated else null.
    116      */
    117     public Principal authenticate(UserRealm realm,
    118                                   String pathInContext,
    119                                   Request request,
    120                                   Response response)
    121         throws IOException
    122     {
    123         // Handle paths
    124         String uri = pathInContext;
    125
    126         // Setup session
    127         HttpSession session=request.getSession(response!=null);
    128         if (session==null)
    129             return null;
    130
    131         // Handle a request for authentication.
    132         // TODO perhaps j_securitycheck can be uri suffix?
    133         if ( uri.endsWith(__J_SECURITY_CHECK) )
    134         {
    135             // Check the session object for login info.
    136             FormCredential form_cred=new FormCredential();
    137             form_cred.authenticate(realm,
    138                                             request.getParameter(__J_USERNAME),
    139                                             request.getParameter(__J_PASSWORD),
    140                                             request);
    141
    142             String nuri=(String)session.getAttribute(__J_URI);
    143             if (nuri==null || nuri.length()==0)
    144             {
    145                 nuri=request.getContextPath();
    146                 if (nuri.length()==0)
    147                     nuri=URIUtil.SLASH;
    148             }
    149
    150             if (form_cred._userPrincipal!=null)
    151             {
    152                 // Authenticated OK
    153                 if(Log.isDebugEnabled())Log.debug("Form authentication OK for "+form_cred._jUserName);
    154                 session.removeAttribute(__J_URI); // Remove popped return URI.
    155                 request.setAuthType(Constraint.__FORM_AUTH);
    156                 request.setUserPrincipal(form_cred._userPrincipal);
    157                 session.setAttribute(__J_AUTHENTICATED,form_cred);
    158
    159                 // Sign-on to SSO mechanism
    160                 if (realm instanceof SSORealm)
    161                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
    162
    163                 // Redirect to original request
    164                 if (response != null) {
    165                     response.setContentLength(0);
    166                     response.sendRedirect(response.encodeRedirectURL(nuri));
    167                 }
    168             }
    169             else
    170             {
    171                 if(Log.isDebugEnabled())Log.debug("Form authentication FAILED for "+StringUtil.printable(form_cred._jUserName));
    172                 if (_formErrorPage==null)
    173                 {
    174                     if (response != null)
    175                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
    176                 }
    177                 else
    178                 {
    179                     if (response != null)
    180                         response.setContentLength(0);
    181                         response.sendRedirect(response.encodeRedirectURL
    182                                           (URIUtil.addPaths(request.getContextPath(),
    183                                                         _formErrorPage)));
    184                 }
    185             }
    186
    187             // Security check is always false, only true after final redirection.
    188             return null;
    189         }
    190
    191         // Check if the session is already authenticated.
    192         FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
    193
    194         if (form_cred != null)
    195         {
    196             // We have a form credential. Has it been distributed?
    197             if (form_cred._userPrincipal==null)
    198             {
    199                 // This form_cred appears to have been distributed.  Need to reauth
    200                 form_cred.authenticate(realm, request);
    201
    202                 // Sign-on to SSO mechanism
    203                 if (form_cred._userPrincipal!=null && realm instanceof SSORealm)
    204                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
    205
    206             }
    207             else if (!realm.reauthenticate(form_cred._userPrincipal))
    208                 // Else check that it is still authenticated.
    209                 form_cred._userPrincipal=null;
    210
    211             // If this credential is still authenticated
    212             if (form_cred._userPrincipal!=null)
    213             {
    214                 if(Log.isDebugEnabled())Log.debug("FORM Authenticated for "+form_cred._userPrincipal.getName());
    215                 request.setAuthType(Constraint.__FORM_AUTH);
    216                 request.setUserPrincipal(form_cred._userPrincipal);
    217                 return form_cred._userPrincipal;
    218             }
    219             else
    220                 session.setAttribute(__J_AUTHENTICATED,null);
    221         }
    222         else if (realm instanceof SSORealm)
    223         {
    224             // Try a single sign on.
    225             Credential cred = ((SSORealm)realm).getSingleSignOn(request,response);
    226
    227             if (request.getUserPrincipal()!=null)
    228             {
    229                 form_cred=new FormCredential();
    230                 form_cred._userPrincipal=request.getUserPrincipal();
    231                 form_cred._jUserName=form_cred._userPrincipal.getName();
    232                 if (cred!=null)
    233                     form_cred._jPassword=cred.toString();
    234                 if(Log.isDebugEnabled())Log.debug("SSO for "+form_cred._userPrincipal);
    235
    236                 request.setAuthType(Constraint.__FORM_AUTH);
    237                 session.setAttribute(__J_AUTHENTICATED,form_cred);
    238                 return form_cred._userPrincipal;
    239             }
    240         }
    241
    242         // Don't authenticate authform or errorpage
    243         if (isLoginOrErrorPage(pathInContext))
    244             return SecurityHandler.__NOBODY;
    245
    246         // redirect to login page
    247         if (response!=null)
    248         {
    249             if (request.getQueryString()!=null)
    250                 uri+="?"+request.getQueryString();
    251             session.setAttribute(__J_URI,
    252                                  request.getScheme() +
    253                                  "://" + request.getServerName() +
    254                                  ":" + request.getServerPort() +
    255                                  URIUtil.addPaths(request.getContextPath(),uri));
    256             response.setContentLength(0);
    257             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),
    258                                                                           _formLoginPage)));
    259         }
    260
    261         return null;
    262     }
    263
    264     public boolean isLoginOrErrorPage(String pathInContext)
    265     {
    266         return pathInContext!=null &&
    267          (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
    268     }
    269
    270     /* ------------------------------------------------------------ */
    271     /** FORM Authentication credential holder.
    272      */
    273     private static class FormCredential implements Serializable, HttpSessionBindingListener
    274     {
    275         String _jUserName;
    276         String _jPassword;
    277         transient Principal _userPrincipal;
    278         transient UserRealm _realm;
    279
    280         void authenticate(UserRealm realm,String user,String password,Request request)
    281         {
    282             _jUserName=user;
    283             _jPassword=password;
    284             _userPrincipal = realm.authenticate(user, password, request);
    285             if (_userPrincipal!=null)
    286                 _realm=realm;
    287             else
    288             {
    289                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(user));
    290                 request.setUserPrincipal(null);
    291             }
    292         }
    293
    294         void authenticate(UserRealm realm,Request request)
    295         {
    296             _userPrincipal = realm.authenticate(_jUserName, _jPassword, request);
    297             if (_userPrincipal!=null)
    298                 _realm=realm;
    299             else
    300             {
    301                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(_jUserName));
    302                 request.setUserPrincipal(null);
    303             }
    304         }
    305
    306
    307         public void valueBound(HttpSessionBindingEvent event) {}
    308
    309         public void valueUnbound(HttpSessionBindingEvent event)
    310         {
    311             if(Log.isDebugEnabled())Log.debug("Logout "+_jUserName);
    312
    313             if(_realm instanceof SSORealm)
    314                 ((SSORealm)_realm).clearSingleSignOn(_jUserName);
    315
    316             if(_realm!=null && _userPrincipal!=null)
    317                 _realm.logout(_userPrincipal);
    318         }
    319
    320         public int hashCode()
    321         {
    322             return _jUserName.hashCode()+_jPassword.hashCode();
    323         }
    324
    325         public boolean equals(Object o)
    326         {
    327             if (!(o instanceof FormCredential))
    328                 return false;
    329             FormCredential fc = (FormCredential)o;
    330             return
    331                 _jUserName.equals(fc._jUserName) &&
    332                 _jPassword.equals(fc._jPassword);
    333         }
    334
    335         public String toString()
    336         {
    337             return "Cred["+_jUserName+"]";
    338         }
    339
    340     }
    341 }