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 }