View Javadoc

1   // ========================================================================
2   // Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.security.authentication;
15  
16  import java.io.IOException;
17  import java.util.Collections;
18  import java.util.Enumeration;
19  
20  import javax.servlet.RequestDispatcher;
21  import javax.servlet.ServletException;
22  import javax.servlet.ServletRequest;
23  import javax.servlet.ServletResponse;
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletRequestWrapper;
26  import javax.servlet.http.HttpServletResponse;
27  import javax.servlet.http.HttpServletResponseWrapper;
28  import javax.servlet.http.HttpSession;
29  
30  import org.eclipse.jetty.http.HttpHeaders;
31  import org.eclipse.jetty.http.security.Constraint;
32  import org.eclipse.jetty.security.Authenticator;
33  import org.eclipse.jetty.security.UserAuthentication;
34  import org.eclipse.jetty.security.ServerAuthException;
35  import org.eclipse.jetty.server.Authentication;
36  import org.eclipse.jetty.server.UserIdentity;
37  import org.eclipse.jetty.server.Authentication.User;
38  import org.eclipse.jetty.util.StringUtil;
39  import org.eclipse.jetty.util.URIUtil;
40  import org.eclipse.jetty.util.log.Log;
41  
42  /**
43   * FORM Authenticator.
44   * 
45   * The form authenticator redirects unauthenticated requests to a log page
46   * which should use a form to gather username/password from the user and send them
47   * to the /j_security_check URI within the context.  FormAuthentication is intended
48   * to be used together with the {@link SessionCachingAuthenticator} so that the
49   * auth results may be associated with the session.
50   *  
51   * This authenticator implements form authentication using dispatchers unless 
52   * the {@link #__FORM_DISPATCH} init parameters is set to false.
53   * 
54   */
55  public class FormAuthenticator extends LoginAuthenticator
56  {
57      public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
58      public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
59      public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
60      public final static String __J_URI = "org.eclipse.jetty.util.URI";
61      public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth";
62      public final static String __J_SECURITY_CHECK = "/j_security_check";
63      public final static String __J_USERNAME = "j_username";
64      public final static String __J_PASSWORD = "j_password";
65      private String _formErrorPage;
66      private String _formErrorPath;
67      private String _formLoginPage;
68      private String _formLoginPath;
69      private boolean _dispatch;
70  
71      public FormAuthenticator()
72      {
73      }
74  
75      /* ------------------------------------------------------------ */
76      public FormAuthenticator(String login,String error)
77      {
78          if (login!=null)
79              setLoginPage(login);
80          if (error!=null)
81              setErrorPage(error);
82      }
83      
84      /* ------------------------------------------------------------ */
85      /**
86       * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.Configuration)
87       */
88      @Override
89      public void setConfiguration(Configuration configuration)
90      {
91          super.setConfiguration(configuration);
92          String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
93          if (login!=null)
94              setLoginPage(login);
95          String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
96          if (error!=null)
97              setErrorPage(error);
98          String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
99          _dispatch=dispatch==null || Boolean.getBoolean(dispatch);
100     }
101 
102     /* ------------------------------------------------------------ */
103     public String getAuthMethod()
104     {
105         return Constraint.__FORM_AUTH;
106     }
107 
108     /* ------------------------------------------------------------ */
109     private void setLoginPage(String path)
110     {
111         if (!path.startsWith("/"))
112         {
113             Log.warn("form-login-page must start with /");
114             path = "/" + path;
115         }
116         _formLoginPage = path;
117         _formLoginPath = path;
118         if (_formLoginPath.indexOf('?') > 0) 
119             _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
120     }
121 
122     /* ------------------------------------------------------------ */
123     private void setErrorPage(String path)
124     {
125         if (path == null || path.trim().length() == 0)
126         {
127             _formErrorPath = null;
128             _formErrorPage = null;
129         }
130         else
131         {
132             if (!path.startsWith("/"))
133             {
134                 Log.warn("form-error-page must start with /");
135                 path = "/" + path;
136             }
137             _formErrorPage = path;
138             _formErrorPath = path;
139 
140             if (_formErrorPath.indexOf('?') > 0) 
141                 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
142         }
143     }
144 
145     /* ------------------------------------------------------------ */
146     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
147     {
148         HttpServletRequest request = (HttpServletRequest)req;
149         HttpServletResponse response = (HttpServletResponse)res;
150         HttpSession session = request.getSession(mandatory);
151         String uri = request.getPathInfo();
152         
153         // not mandatory or not authenticated
154         if (session == null || isLoginOrErrorPage(uri)) 
155         {
156             return Authentication.NOT_CHECKED;
157         }
158             
159 
160         try
161         {
162             // Handle a request for authentication.
163             if (uri==null)
164                 uri=URIUtil.SLASH;
165             else if (uri.endsWith(__J_SECURITY_CHECK))
166             {
167                 final String username = request.getParameter(__J_USERNAME);
168                 final String password = request.getParameter(__J_PASSWORD);
169                 
170                 UserIdentity user = _loginService.login(username,password);
171                 if (user!=null)
172                 {
173                     // Redirect to original request
174                     String nuri;
175                     synchronized(session)
176                     {
177                         nuri = (String) session.getAttribute(__J_URI);
178                         session.removeAttribute(__J_URI);
179                     }
180                     
181                     if (nuri == null || nuri.length() == 0)
182                     {
183                         nuri = request.getContextPath();
184                         if (nuri.length() == 0) 
185                             nuri = URIUtil.SLASH;
186                     }
187                     response.setContentLength(0);   
188                     response.sendRedirect(response.encodeRedirectURL(nuri));
189                     return new FormAuthentication(this,user);
190                 }
191                 
192                 // not authenticated
193                 if (Log.isDebugEnabled()) 
194                     Log.debug("Form authentication FAILED for " + StringUtil.printable(username));
195                 if (_formErrorPage == null)
196                 {
197                     if (response != null) 
198                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
199                 }
200                 else if (_dispatch)
201                 {
202                     RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
203                     response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
204                     response.setDateHeader(HttpHeaders.EXPIRES,1);
205                     dispatcher.forward(new FormRequest(request), new FormResponse(response));
206                 }
207                 else
208                 {
209                     response.sendRedirect(URIUtil.addPaths(request.getContextPath(),_formErrorPage));
210                 }
211                 
212                 return Authentication.SEND_FAILURE;
213             }
214             
215             if (mandatory) 
216             {
217                 // redirect to login page
218                 if (request.getQueryString() != null)
219                     uri += "?" + request.getQueryString();
220                 
221                 synchronized (session)
222                 {
223                     if (session.getAttribute(__J_URI)==null)
224                         session.setAttribute(__J_URI, request.getScheme() + "://"
225                                 + request.getServerName()
226                                 + ":"
227                                 + request.getServerPort()
228                                 + URIUtil.addPaths(request.getContextPath(), uri));
229                 }
230 
231                 if (_dispatch)
232                 {
233                     RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
234                     response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
235                     response.setDateHeader(HttpHeaders.EXPIRES,1);
236                     dispatcher.forward(new FormRequest(request), new FormResponse(response));
237                 }
238                 else
239                 {
240                     response.sendRedirect(URIUtil.addPaths(request.getContextPath(),_formLoginPage));
241                 }
242                 return Authentication.SEND_CONTINUE;
243             }
244             
245             return Authentication.UNAUTHENTICATED;            
246         }
247         catch (IOException e)
248         {
249             throw new ServerAuthException(e);
250         }
251         catch (ServletException e)
252         {
253             throw new ServerAuthException(e);
254         }
255     }
256 
257     /* ------------------------------------------------------------ */
258     public boolean isLoginOrErrorPage(String pathInContext)
259     {
260         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
261     }
262 
263     /* ------------------------------------------------------------ */
264     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
265     {
266         return true;
267     }
268 
269     /* ------------------------------------------------------------ */
270     /* ------------------------------------------------------------ */
271     protected static class FormRequest extends HttpServletRequestWrapper
272     {
273         public FormRequest(HttpServletRequest request)
274         {
275             super(request);
276         }
277 
278         @Override
279         public long getDateHeader(String name)
280         {
281             if (name.toLowerCase().startsWith("if-"))
282                 return -1;
283             return super.getDateHeader(name);
284         }
285         
286         @Override
287         public String getHeader(String name)
288         {
289             if (name.toLowerCase().startsWith("if-"))
290                 return null;
291             return super.getHeader(name);
292         }
293 
294         @Override
295         public Enumeration getHeaderNames()
296         {
297             return Collections.enumeration(Collections.list(super.getHeaderNames()));
298         }
299 
300         @Override
301         public Enumeration getHeaders(String name)
302         {
303             if (name.toLowerCase().startsWith("if-"))
304                 return Collections.enumeration(Collections.EMPTY_LIST);
305             return super.getHeaders(name);
306         }
307     }
308 
309     /* ------------------------------------------------------------ */
310     /* ------------------------------------------------------------ */
311     protected static class FormResponse extends HttpServletResponseWrapper
312     {
313         public FormResponse(HttpServletResponse response)
314         {
315             super(response);
316         }
317 
318         @Override
319         public void addDateHeader(String name, long date)
320         {
321             if (notIgnored(name))
322                 super.addDateHeader(name,date);
323         }
324 
325         @Override
326         public void addHeader(String name, String value)
327         {
328             if (notIgnored(name))
329                 super.addHeader(name,value);
330         }
331 
332         @Override
333         public void setDateHeader(String name, long date)
334         {
335             if (notIgnored(name))
336                 super.setDateHeader(name,date);
337         }
338         
339         @Override
340         public void setHeader(String name, String value)
341         {
342             if (notIgnored(name))
343                 super.setHeader(name,value);
344         }
345         
346         private boolean notIgnored(String name)
347         {
348             if (HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name) ||
349                 HttpHeaders.PRAGMA.equalsIgnoreCase(name) ||
350                 HttpHeaders.ETAG.equalsIgnoreCase(name) ||
351                 HttpHeaders.EXPIRES.equalsIgnoreCase(name) ||
352                 HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(name) ||
353                 HttpHeaders.AGE.equalsIgnoreCase(name))
354                 return false;
355             return true;
356         }
357     }
358     
359     public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
360     {
361         public FormAuthentication(Authenticator authenticator, UserIdentity userIdentity)
362         {
363             super(authenticator,userIdentity);
364         }
365         
366         public String toString()
367         {
368             return "Form"+super.toString();
369         }
370     }
371 }