]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / security / authentication / FormAuthenticator.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19 package org.eclipse.jetty.security.authentication;
20
21 import java.io.IOException;
22 import java.util.Collections;
23 import java.util.Enumeration;
24 import java.util.Locale;
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.ServletException;
27 import javax.servlet.ServletRequest;
28 import javax.servlet.ServletResponse;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletRequestWrapper;
31 import javax.servlet.http.HttpServletResponse;
32 import javax.servlet.http.HttpServletResponseWrapper;
33 import javax.servlet.http.HttpSession;
34
35 import org.eclipse.jetty.http.HttpHeader;
36 import org.eclipse.jetty.http.HttpHeaderValue;
37 import org.eclipse.jetty.http.HttpMethod;
38 import org.eclipse.jetty.http.HttpVersion;
39 import org.eclipse.jetty.http.MimeTypes;
40 import org.eclipse.jetty.security.ServerAuthException;
41 import org.eclipse.jetty.security.UserAuthentication;
42 import org.eclipse.jetty.server.Authentication;
43 import org.eclipse.jetty.server.Authentication.User;
44 import org.eclipse.jetty.server.HttpChannel;
45 import org.eclipse.jetty.server.Request;
46 import org.eclipse.jetty.server.Response;
47 import org.eclipse.jetty.server.UserIdentity;
48 import org.eclipse.jetty.util.MultiMap;
49 import org.eclipse.jetty.util.StringUtil;
50 import org.eclipse.jetty.util.URIUtil;
51 import org.eclipse.jetty.util.log.Log;
52 import org.eclipse.jetty.util.log.Logger;
53 import org.eclipse.jetty.util.security.Constraint;
54
55 /**
56  * FORM Authenticator.
57  *
58  * <p>This authenticator implements form authentication will use dispatchers to
59  * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
60  * Otherwise it will redirect.</p>
61  *
62  * <p>The form authenticator redirects unauthenticated requests to a log page
63  * which should use a form to gather username/password from the user and send them
64  * to the /j_security_check URI within the context.  FormAuthentication uses
65  * {@link SessionAuthentication} to wrap Authentication results so that they
66  * are  associated with the session.</p>
67  *
68  *
69  */
70 public class FormAuthenticator extends LoginAuthenticator
71 {
72     private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
73
74     public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
75     public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
76     public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
77     public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
78     public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
79     public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
80     public final static String __J_SECURITY_CHECK = "/j_security_check";
81     public final static String __J_USERNAME = "j_username";
82     public final static String __J_PASSWORD = "j_password";
83
84     private String _formErrorPage;
85     private String _formErrorPath;
86     private String _formLoginPage;
87     private String _formLoginPath;
88     private boolean _dispatch;
89     private boolean _alwaysSaveUri;
90
91     public FormAuthenticator()
92     {
93     }
94
95     /* ------------------------------------------------------------ */
96     public FormAuthenticator(String login,String error,boolean dispatch)
97     {
98         this();
99         if (login!=null)
100             setLoginPage(login);
101         if (error!=null)
102             setErrorPage(error);
103         _dispatch=dispatch;
104     }
105
106     /* ------------------------------------------------------------ */
107     /**
108      * If true, uris that cause a redirect to a login page will always
109      * be remembered. If false, only the first uri that leads to a login
110      * page redirect is remembered.
111      * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
112      * @param alwaysSave
113      */
114     public void setAlwaysSaveUri (boolean alwaysSave)
115     {
116         _alwaysSaveUri = alwaysSave;
117     }
118
119
120     /* ------------------------------------------------------------ */
121     public boolean getAlwaysSaveUri ()
122     {
123         return _alwaysSaveUri;
124     }
125
126     /* ------------------------------------------------------------ */
127     /**
128      * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
129      */
130     @Override
131     public void setConfiguration(AuthConfiguration configuration)
132     {
133         super.setConfiguration(configuration);
134         String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
135         if (login!=null)
136             setLoginPage(login);
137         String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
138         if (error!=null)
139             setErrorPage(error);
140         String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
141         _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
142     }
143
144     /* ------------------------------------------------------------ */
145     @Override
146     public String getAuthMethod()
147     {
148         return Constraint.__FORM_AUTH;
149     }
150
151     /* ------------------------------------------------------------ */
152     private void setLoginPage(String path)
153     {
154         if (!path.startsWith("/"))
155         {
156             LOG.warn("form-login-page must start with /");
157             path = "/" + path;
158         }
159         _formLoginPage = path;
160         _formLoginPath = path;
161         if (_formLoginPath.indexOf('?') > 0)
162             _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
163     }
164
165     /* ------------------------------------------------------------ */
166     private void setErrorPage(String path)
167     {
168         if (path == null || path.trim().length() == 0)
169         {
170             _formErrorPath = null;
171             _formErrorPage = null;
172         }
173         else
174         {
175             if (!path.startsWith("/"))
176             {
177                 LOG.warn("form-error-page must start with /");
178                 path = "/" + path;
179             }
180             _formErrorPage = path;
181             _formErrorPath = path;
182
183             if (_formErrorPath.indexOf('?') > 0)
184                 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
185         }
186     }
187     
188     
189     /* ------------------------------------------------------------ */
190     @Override
191     public UserIdentity login(String username, Object password, ServletRequest request)
192     {
193         
194         UserIdentity user = super.login(username,password,request);
195         if (user!=null)
196         {
197             HttpSession session = ((HttpServletRequest)request).getSession(true);
198             Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
199             session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
200         }
201         return user;
202     }
203     
204     
205     /* ------------------------------------------------------------ */
206     @Override
207     public void prepareRequest(ServletRequest request)
208     {
209         //if this is a request resulting from a redirect after auth is complete
210         //(ie its from a redirect to the original request uri) then due to 
211         //browser handling of 302 redirects, the method may not be the same as
212         //that of the original request. Replace the method and original post
213         //params (if it was a post).
214         //
215         //See Servlet Spec 3.1 sec 13.6.3
216         HttpServletRequest httpRequest = (HttpServletRequest)request;
217         HttpSession session = httpRequest.getSession(false);
218         if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
219             return; //not authenticated yet
220         
221         String juri = (String)session.getAttribute(__J_URI);
222         if (juri == null || juri.length() == 0)
223             return; //no original uri saved
224         
225         String method = (String)session.getAttribute(__J_METHOD);
226         if (method == null || method.length() == 0)
227             return; //didn't save original request method
228        
229         StringBuffer buf = httpRequest.getRequestURL();
230         if (httpRequest.getQueryString() != null)
231             buf.append("?").append(httpRequest.getQueryString());
232         
233         if (!juri.equals(buf.toString()))
234             return; //this request is not for the same url as the original
235         
236         //restore the original request's method on this request
237         if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
238         Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
239         HttpMethod m = HttpMethod.fromString(method);
240         base_request.setMethod(m,m.asString());
241     }
242
243     /* ------------------------------------------------------------ */
244     @Override
245     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
246     {
247         HttpServletRequest request = (HttpServletRequest)req;
248         HttpServletResponse response = (HttpServletResponse)res;
249         String uri = request.getRequestURI();
250         if (uri==null)
251             uri=URIUtil.SLASH;
252
253         mandatory|=isJSecurityCheck(uri);
254         if (!mandatory)
255             return new DeferredAuthentication(this);
256
257         if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
258             return new DeferredAuthentication(this);
259
260         HttpSession session = request.getSession(true);
261
262         try
263         {
264             // Handle a request for authentication.
265             if (isJSecurityCheck(uri))
266             {
267                 final String username = request.getParameter(__J_USERNAME);
268                 final String password = request.getParameter(__J_PASSWORD);
269
270                 UserIdentity user = login(username, password, request);
271                 LOG.debug("jsecuritycheck {} {}",username,user);
272                 session = request.getSession(true);
273                 if (user!=null)
274                 {                    
275                     // Redirect to original request
276                     String nuri;
277                     FormAuthentication form_auth;
278                     synchronized(session)
279                     {
280                         nuri = (String) session.getAttribute(__J_URI);
281
282                         if (nuri == null || nuri.length() == 0)
283                         {
284                             nuri = request.getContextPath();
285                             if (nuri.length() == 0)
286                                 nuri = URIUtil.SLASH;
287                         }
288                         form_auth = new FormAuthentication(getAuthMethod(),user);
289                     }
290                     LOG.debug("authenticated {}->{}",form_auth,nuri);
291
292                     response.setContentLength(0);
293                     Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
294                     Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
295                     int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
296                     base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
297                     return form_auth;
298                 }
299
300                 // not authenticated
301                 if (LOG.isDebugEnabled())
302                     LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
303                 if (_formErrorPage == null)
304                 {
305                     LOG.debug("auth failed {}->403",username);
306                     if (response != null)
307                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
308                 }
309                 else if (_dispatch)
310                 {
311                     LOG.debug("auth failed {}=={}",username,_formErrorPage);
312                     RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
313                     response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
314                     response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
315                     dispatcher.forward(new FormRequest(request), new FormResponse(response));
316                 }
317                 else
318                 {
319                     LOG.debug("auth failed {}->{}",username,_formErrorPage);
320                     Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
321                     Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
322                     int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
323                     base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
324                 }
325
326                 return Authentication.SEND_FAILURE;
327             }
328
329             // Look for cached authentication
330             Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
331             if (authentication != null)
332             {
333                 // Has authentication been revoked?
334                 if (authentication instanceof Authentication.User &&
335                     _loginService!=null &&
336                     !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
337                 {
338                     LOG.debug("auth revoked {}",authentication);
339                     session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
340                 }
341                 else
342                 {
343                     synchronized (session)
344                     {
345                         String j_uri=(String)session.getAttribute(__J_URI);
346                         if (j_uri!=null)
347                         {
348                             //check if the request is for the same url as the original and restore
349                             //params if it was a post
350                             LOG.debug("auth retry {}->{}",authentication,j_uri);
351                             StringBuffer buf = request.getRequestURL();
352                             if (request.getQueryString() != null)
353                                 buf.append("?").append(request.getQueryString());
354
355                             if (j_uri.equals(buf.toString()))
356                             {
357                                 MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
358                                 if (j_post!=null)
359                                 {
360                                     LOG.debug("auth rePOST {}->{}",authentication,j_uri);
361                                     Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
362                                     base_request.setContentParameters(j_post);
363                                 }
364                                 session.removeAttribute(__J_URI);
365                                 session.removeAttribute(__J_METHOD);
366                                 session.removeAttribute(__J_POST);
367                             }
368                         }
369                     }
370                     LOG.debug("auth {}",authentication);
371                     return authentication;
372                 }
373             }
374
375             // if we can't send challenge
376             if (DeferredAuthentication.isDeferred(response))
377             {
378                 LOG.debug("auth deferred {}",session.getId());
379                 return Authentication.UNAUTHENTICATED;
380             }
381
382             // remember the current URI
383             synchronized (session)
384             {
385                 // But only if it is not set already, or we save every uri that leads to a login form redirect
386                 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
387                 {
388                     StringBuffer buf = request.getRequestURL();
389                     if (request.getQueryString() != null)
390                         buf.append("?").append(request.getQueryString());
391                     session.setAttribute(__J_URI, buf.toString());
392                     session.setAttribute(__J_METHOD, request.getMethod());
393
394                     if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
395                     {
396                         Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
397                         MultiMap<String> formParameters = new MultiMap<>();
398                         base_request.extractFormParameters(formParameters);
399                         session.setAttribute(__J_POST, formParameters);
400                     }
401                 }
402             }
403
404             // send the the challenge
405             if (_dispatch)
406             {
407                 LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
408                 RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
409                 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
410                 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
411                 dispatcher.forward(new FormRequest(request), new FormResponse(response));
412             }
413             else
414             {
415                 LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
416                 Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
417                 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
418                 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
419                 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
420             }
421             return Authentication.SEND_CONTINUE;
422         }
423         catch (IOException | ServletException e)
424         {
425             throw new ServerAuthException(e);
426         }
427     }
428
429     /* ------------------------------------------------------------ */
430     public boolean isJSecurityCheck(String uri)
431     {
432         int jsc = uri.indexOf(__J_SECURITY_CHECK);
433
434         if (jsc<0)
435             return false;
436         int e=jsc+__J_SECURITY_CHECK.length();
437         if (e==uri.length())
438             return true;
439         char c = uri.charAt(e);
440         return c==';'||c=='#'||c=='/'||c=='?';
441     }
442
443     /* ------------------------------------------------------------ */
444     public boolean isLoginOrErrorPage(String pathInContext)
445     {
446         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
447     }
448
449     /* ------------------------------------------------------------ */
450     @Override
451     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
452     {
453         return true;
454     }
455
456     /* ------------------------------------------------------------ */
457     /* ------------------------------------------------------------ */
458     protected static class FormRequest extends HttpServletRequestWrapper
459     {
460         public FormRequest(HttpServletRequest request)
461         {
462             super(request);
463         }
464
465         @Override
466         public long getDateHeader(String name)
467         {
468             if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
469                 return -1;
470             return super.getDateHeader(name);
471         }
472
473         @Override
474         public String getHeader(String name)
475         {
476             if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
477                 return null;
478             return super.getHeader(name);
479         }
480
481         @Override
482         public Enumeration<String> getHeaderNames()
483         {
484             return Collections.enumeration(Collections.list(super.getHeaderNames()));
485         }
486
487         @Override
488         public Enumeration<String> getHeaders(String name)
489         {
490             if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
491                 return Collections.<String>enumeration(Collections.<String>emptyList());
492             return super.getHeaders(name);
493         }
494     }
495
496     /* ------------------------------------------------------------ */
497     /* ------------------------------------------------------------ */
498     protected static class FormResponse extends HttpServletResponseWrapper
499     {
500         public FormResponse(HttpServletResponse response)
501         {
502             super(response);
503         }
504
505         @Override
506         public void addDateHeader(String name, long date)
507         {
508             if (notIgnored(name))
509                 super.addDateHeader(name,date);
510         }
511
512         @Override
513         public void addHeader(String name, String value)
514         {
515             if (notIgnored(name))
516                 super.addHeader(name,value);
517         }
518
519         @Override
520         public void setDateHeader(String name, long date)
521         {
522             if (notIgnored(name))
523                 super.setDateHeader(name,date);
524         }
525
526         @Override
527         public void setHeader(String name, String value)
528         {
529             if (notIgnored(name))
530                 super.setHeader(name,value);
531         }
532
533         private boolean notIgnored(String name)
534         {
535             if (HttpHeader.CACHE_CONTROL.is(name) ||
536                 HttpHeader.PRAGMA.is(name) ||
537                 HttpHeader.ETAG.is(name) ||
538                 HttpHeader.EXPIRES.is(name) ||
539                 HttpHeader.LAST_MODIFIED.is(name) ||
540                 HttpHeader.AGE.is(name))
541                 return false;
542             return true;
543         }
544     }
545
546     /* ------------------------------------------------------------ */
547     /** This Authentication represents a just completed Form authentication.
548      * Subsequent requests from the same user are authenticated by the presents
549      * of a {@link SessionAuthentication} instance in their session.
550      */
551     public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
552     {
553         public FormAuthentication(String method, UserIdentity userIdentity)
554         {
555             super(method,userIdentity);
556         }
557
558         @Override
559         public String toString()
560         {
561             return "Form"+super.toString();
562         }
563     }
564 }