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