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         if (isJSecurityCheck(uri) )
    133         {
    134             // Check the session object for login info.
    135             FormCredential form_cred=new FormCredential();
    136             form_cred.authenticate(realm,
    137                     request.getParameter(__J_USERNAME),
    138                     request.getParameter(__J_PASSWORD),
    139                     request);
    140
    141             String nuri=(String)session.getAttribute(__J_URI);
    142             if (nuri==null || nuri.length()==0)
    143             {
    144                 nuri=request.getContextPath();
    145                 if (nuri.length()==0)
    146                     nuri=URIUtil.SLASH;
    147             }
    148
    149             if (form_cred._userPrincipal!=null)
    150             {
    151                 // Authenticated OK
    152                 if(Log.isDebugEnabled())Log.debug("Form authentication OK for "+form_cred._jUserName);
    153                 session.removeAttribute(__J_URI); // Remove popped return URI.
    154                 request.setAuthType(Constraint.__FORM_AUTH);
    155                 request.setUserPrincipal(form_cred._userPrincipal);
    156                 session.setAttribute(__J_AUTHENTICATED,form_cred);
    157
    158                 // Sign-on to SSO mechanism
    159                 if (realm instanceof SSORealm)
    160                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
    161
    162                 // Redirect to original request
    163                 if (response != null)
    164                 {
    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
    173                 if (response!=null)
    174                 {
    175                     if (_formErrorPage==null)
    176                     {
    177                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
    178                     }
    179                     else
    180                     {
    181                         response.setContentLength(0);
    182                         response.sendRedirect(response.encodeRedirectURL
    183                                 (URIUtil.addPaths(request.getContextPath(),
    184                                         _formErrorPage)));
    185                     }
    186                 }
    187             }
    188
    189             // Security check is always false, only true after final redirection.
    190             return null;
    191         }
    192
    193         // Check if the session is already authenticated.
    194         FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
    195
    196         if (form_cred != null)
    197         {
    198             // We have a form credential. Has it been distributed?
    199             if (form_cred._userPrincipal==null)
    200             {
    201                 // This form_cred appears to have been distributed.  Need to reauth
    202                 form_cred.authenticate(realm, request);
    203
    204                 // Sign-on to SSO mechanism
    205                 if (form_cred._userPrincipal!=null && realm instanceof SSORealm)
    206                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
    207
    208             }
    209             else if (!realm.reauthenticate(form_cred._userPrincipal))
    210                 // Else check that it is still authenticated.
    211                 form_cred._userPrincipal=null;
    212
    213             // If this credential is still authenticated
    214             if (form_cred._userPrincipal!=null)
    215             {
    216                 if(Log.isDebugEnabled())Log.debug("FORM Authenticated for "+form_cred._userPrincipal.getName());
    217                 request.setAuthType(Constraint.__FORM_AUTH);
    218                 request.setUserPrincipal(form_cred._userPrincipal);
    219                 return form_cred._userPrincipal;
    220             }
    221             else
    222                 session.setAttribute(__J_AUTHENTICATED,null);
    223         }
    224         else if (realm instanceof SSORealm)
    225         {
    226             // Try a single sign on.
    227             Credential cred = ((SSORealm)realm).getSingleSignOn(request,response);
    228
    229             if (request.getUserPrincipal()!=null)
    230             {
    231                 form_cred=new FormCredential();
    232                 form_cred._userPrincipal=request.getUserPrincipal();
    233                 form_cred._jUserName=form_cred._userPrincipal.getName();
    234                 if (cred!=null)
    235                     form_cred._jPassword=cred.toString();
    236                 if(Log.isDebugEnabled())Log.debug("SSO for "+form_cred._userPrincipal);
    237
    238                 request.setAuthType(Constraint.__FORM_AUTH);
    239                 session.setAttribute(__J_AUTHENTICATED,form_cred);
    240                 return form_cred._userPrincipal;
    241             }
    242         }
    243
    244         // Don't authenticate authform or errorpage
    245         if (isLoginOrErrorPage(pathInContext))
    246             return SecurityHandler.__NOBODY;
    247
    248         // redirect to login page
    249         if (response!=null)
    250         {
    251             if (request.getQueryString()!=null)
    252                 uri+="?"+request.getQueryString();
    253             session.setAttribute(__J_URI,
    254                                  request.getScheme() +
    255                                  "://" + request.getServerName() +
    256                                  ":" + request.getServerPort() +
    257                                  URIUtil.addPaths(request.getContextPath(),uri));
    258             response.setContentLength(0);
    259             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),
    260                                                                           _formLoginPage)));
    261         }
    262
    263         return null;
    264     }
    265
    266     /* ------------------------------------------------------------ */
    267     public boolean isLoginOrErrorPage(String pathInContext)
    268     {
    269         return pathInContext!=null &&
    270          (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
    271     }
    272
    273     /* ------------------------------------------------------------ */
    274     public boolean isJSecurityCheck(String uri)
    275     {
    276         int jsc = uri.indexOf(__J_SECURITY_CHECK);
    277
    278         if (jsc<0)
    279             return false;
    280         int e=jsc+__J_SECURITY_CHECK.length();
    281         if (e==uri.length())
    282             return true;
    283         char c = uri.charAt(e);
    284         return c==';'||c=='#'||c=='/'||c=='?';
    285     }
    286
    287
    288     /* ------------------------------------------------------------ */
    289     /** FORM Authentication credential holder.
    290      */
    291     private static class FormCredential implements Serializable, HttpSessionBindingListener
    292     {
    293         String _jUserName;
    294         String _jPassword;
    295         transient Principal _userPrincipal;
    296         transient UserRealm _realm;
    297
    298         void authenticate(UserRealm realm,String user,String password,Request request)
    299         {
    300             _jUserName=user;
    301             _jPassword=password;
    302             _userPrincipal = realm.authenticate(user, password, request);
    303             if (_userPrincipal!=null)
    304                 _realm=realm;
    305             else
    306             {
    307                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(user));
    308                 request.setUserPrincipal(null);
    309             }
    310         }
    311
    312         void authenticate(UserRealm realm,Request request)
    313         {
    314             _userPrincipal = realm.authenticate(_jUserName, _jPassword, request);
    315             if (_userPrincipal!=null)
    316                 _realm=realm;
    317             else
    318             {
    319                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(_jUserName));
    320                 request.setUserPrincipal(null);
    321             }
    322         }
    323
    324
    325         public void valueBound(HttpSessionBindingEvent event) {}
    326
    327         public void valueUnbound(HttpSessionBindingEvent event)
    328         {
    329             if(Log.isDebugEnabled())Log.debug("Logout "+_jUserName);
    330
    331             if(_realm instanceof SSORealm)
    332                 ((SSORealm)_realm).clearSingleSignOn(_jUserName);
    333
    334             if(_realm!=null && _userPrincipal!=null)
    335                 _realm.logout(_userPrincipal);
    336         }
    337
    338         public int hashCode()
    339         {
    340             return _jUserName.hashCode()+_jPassword.hashCode();
    341         }
    342
    343         public boolean equals(Object o)
    344         {
    345             if (!(o instanceof FormCredential))
    346                 return false;
    347             FormCredential fc = (FormCredential)o;
    348             return
    349                 _jUserName.equals(fc._jUserName) &&
    350                 _jPassword.equals(fc._jPassword);
    351         }
    352
    353         public String toString()
    354         {
    355             return "Cred["+_jUserName+"]";
    356         }
    357
    358     }
    359 }