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 }