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.getRequestURI();
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                 synchronized (session)
219                 {
220                     if (session.getAttribute(__J_URI)==null)
221                     {
222                         StringBuffer buf = request.getRequestURL();
223                         if (request.getQueryString() != null)
224                             buf.append("?").append(request.getQueryString());
225                         session.setAttribute(__J_URI, buf.toString());
226                     }
227                 }
228 
229                 if (_dispatch)
230                 {
231                     RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
232                     response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
233                     response.setDateHeader(HttpHeaders.EXPIRES,1);
234                     dispatcher.forward(new FormRequest(request), new FormResponse(response));
235                 }
236                 else
237                 {
238                     response.sendRedirect(URIUtil.addPaths(request.getContextPath(),_formLoginPage));
239                 }
240                 return Authentication.SEND_CONTINUE;
241             }
242             
243             return Authentication.UNAUTHENTICATED;            
244         }
245         catch (IOException e)
246         {
247             throw new ServerAuthException(e);
248         }
249         catch (ServletException e)
250         {
251             throw new ServerAuthException(e);
252         }
253     }
254 
255     /* ------------------------------------------------------------ */
256     public boolean isLoginOrErrorPage(String pathInContext)
257     {
258         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
259     }
260 
261     /* ------------------------------------------------------------ */
262     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
263     {
264         return true;
265     }
266 
267     /* ------------------------------------------------------------ */
268     /* ------------------------------------------------------------ */
269     protected static class FormRequest extends HttpServletRequestWrapper
270     {
271         public FormRequest(HttpServletRequest request)
272         {
273             super(request);
274         }
275 
276         @Override
277         public long getDateHeader(String name)
278         {
279             if (name.toLowerCase().startsWith("if-"))
280                 return -1;
281             return super.getDateHeader(name);
282         }
283         
284         @Override
285         public String getHeader(String name)
286         {
287             if (name.toLowerCase().startsWith("if-"))
288                 return null;
289             return super.getHeader(name);
290         }
291 
292         @Override
293         public Enumeration getHeaderNames()
294         {
295             return Collections.enumeration(Collections.list(super.getHeaderNames()));
296         }
297 
298         @Override
299         public Enumeration getHeaders(String name)
300         {
301             if (name.toLowerCase().startsWith("if-"))
302                 return Collections.enumeration(Collections.EMPTY_LIST);
303             return super.getHeaders(name);
304         }
305     }
306 
307     /* ------------------------------------------------------------ */
308     /* ------------------------------------------------------------ */
309     protected static class FormResponse extends HttpServletResponseWrapper
310     {
311         public FormResponse(HttpServletResponse response)
312         {
313             super(response);
314         }
315 
316         @Override
317         public void addDateHeader(String name, long date)
318         {
319             if (notIgnored(name))
320                 super.addDateHeader(name,date);
321         }
322 
323         @Override
324         public void addHeader(String name, String value)
325         {
326             if (notIgnored(name))
327                 super.addHeader(name,value);
328         }
329 
330         @Override
331         public void setDateHeader(String name, long date)
332         {
333             if (notIgnored(name))
334                 super.setDateHeader(name,date);
335         }
336         
337         @Override
338         public void setHeader(String name, String value)
339         {
340             if (notIgnored(name))
341                 super.setHeader(name,value);
342         }
343         
344         private boolean notIgnored(String name)
345         {
346             if (HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name) ||
347                 HttpHeaders.PRAGMA.equalsIgnoreCase(name) ||
348                 HttpHeaders.ETAG.equalsIgnoreCase(name) ||
349                 HttpHeaders.EXPIRES.equalsIgnoreCase(name) ||
350                 HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(name) ||
351                 HttpHeaders.AGE.equalsIgnoreCase(name))
352                 return false;
353             return true;
354         }
355     }
356     
357     public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
358     {
359         public FormAuthentication(Authenticator authenticator, UserIdentity userIdentity)
360         {
361             super(authenticator,userIdentity);
362         }
363         
364         public String toString()
365         {
366             return "Form"+super.toString();
367         }
368     }
369 }