1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
26 import javax.servlet.RequestDispatcher;
27 import javax.servlet.ServletException;
28 import javax.servlet.ServletRequest;
29 import javax.servlet.ServletResponse;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletRequestWrapper;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpServletResponseWrapper;
34 import javax.servlet.http.HttpSession;
35
36 import org.eclipse.jetty.http.HttpHeader;
37 import org.eclipse.jetty.http.HttpHeaderValue;
38 import org.eclipse.jetty.http.HttpMethod;
39 import org.eclipse.jetty.http.HttpVersion;
40 import org.eclipse.jetty.http.MimeTypes;
41 import org.eclipse.jetty.security.ServerAuthException;
42 import org.eclipse.jetty.security.UserAuthentication;
43 import org.eclipse.jetty.server.Authentication;
44 import org.eclipse.jetty.server.Authentication.User;
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
57
58
59
60
61
62
63
64
65
66
67
68 public class FormAuthenticator extends LoginAuthenticator
69 {
70 private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
71
72 public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
73 public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
74 public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
75 public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
76 public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
77 public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
78 public final static String __J_SECURITY_CHECK = "/j_security_check";
79 public final static String __J_USERNAME = "j_username";
80 public final static String __J_PASSWORD = "j_password";
81
82 private String _formErrorPage;
83 private String _formErrorPath;
84 private String _formLoginPage;
85 private String _formLoginPath;
86 private boolean _dispatch;
87 private boolean _alwaysSaveUri;
88
89 public FormAuthenticator()
90 {
91 }
92
93
94 public FormAuthenticator(String login,String error,boolean dispatch)
95 {
96 this();
97 if (login!=null)
98 setLoginPage(login);
99 if (error!=null)
100 setErrorPage(error);
101 _dispatch=dispatch;
102 }
103
104
105
106
107
108
109
110
111
112 public void setAlwaysSaveUri (boolean alwaysSave)
113 {
114 _alwaysSaveUri = alwaysSave;
115 }
116
117
118
119 public boolean getAlwaysSaveUri ()
120 {
121 return _alwaysSaveUri;
122 }
123
124
125
126
127
128 @Override
129 public void setConfiguration(AuthConfiguration configuration)
130 {
131 super.setConfiguration(configuration);
132 String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
133 if (login!=null)
134 setLoginPage(login);
135 String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
136 if (error!=null)
137 setErrorPage(error);
138 String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
139 _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
140 }
141
142
143 @Override
144 public String getAuthMethod()
145 {
146 return Constraint.__FORM_AUTH;
147 }
148
149
150 private void setLoginPage(String path)
151 {
152 if (!path.startsWith("/"))
153 {
154 LOG.warn("form-login-page must start with /");
155 path = "/" + path;
156 }
157 _formLoginPage = path;
158 _formLoginPath = path;
159 if (_formLoginPath.indexOf('?') > 0)
160 _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
161 }
162
163
164 private void setErrorPage(String path)
165 {
166 if (path == null || path.trim().length() == 0)
167 {
168 _formErrorPath = null;
169 _formErrorPage = null;
170 }
171 else
172 {
173 if (!path.startsWith("/"))
174 {
175 LOG.warn("form-error-page must start with /");
176 path = "/" + path;
177 }
178 _formErrorPage = path;
179 _formErrorPath = path;
180
181 if (_formErrorPath.indexOf('?') > 0)
182 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
183 }
184 }
185
186
187
188 @Override
189 public UserIdentity login(String username, Object password, ServletRequest request)
190 {
191
192 UserIdentity user = super.login(username,password,request);
193 if (user!=null)
194 {
195 HttpSession session = ((HttpServletRequest)request).getSession(true);
196 Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
197 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
198 }
199 return user;
200 }
201
202
203
204 @Override
205 public void prepareRequest(ServletRequest request)
206 {
207
208
209
210
211
212
213
214 HttpServletRequest httpRequest = (HttpServletRequest)request;
215 HttpSession session = httpRequest.getSession(false);
216 if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
217 return;
218
219 String juri = (String)session.getAttribute(__J_URI);
220 if (juri == null || juri.length() == 0)
221 return;
222
223 String method = (String)session.getAttribute(__J_METHOD);
224 if (method == null || method.length() == 0)
225 return;
226
227 StringBuffer buf = httpRequest.getRequestURL();
228 if (httpRequest.getQueryString() != null)
229 buf.append("?").append(httpRequest.getQueryString());
230
231 if (!juri.equals(buf.toString()))
232 return;
233
234
235 if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
236 Request base_request = Request.getBaseRequest(request);
237 base_request.setMethod(method);
238 }
239
240
241 @Override
242 public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
243 {
244 HttpServletRequest request = (HttpServletRequest)req;
245 HttpServletResponse response = (HttpServletResponse)res;
246 Request base_request = Request.getBaseRequest(request);
247 Response base_response = base_request.getResponse();
248
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
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
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 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
294 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
295 return form_auth;
296 }
297
298
299 if (LOG.isDebugEnabled())
300 LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
301 if (_formErrorPage == null)
302 {
303 LOG.debug("auth failed {}->403",username);
304 if (response != null)
305 response.sendError(HttpServletResponse.SC_FORBIDDEN);
306 }
307 else if (_dispatch)
308 {
309 LOG.debug("auth failed {}=={}",username,_formErrorPage);
310 RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
311 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
312 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
313 dispatcher.forward(new FormRequest(request), new FormResponse(response));
314 }
315 else
316 {
317 LOG.debug("auth failed {}->{}",username,_formErrorPage);
318 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
319 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
320 }
321
322 return Authentication.SEND_FAILURE;
323 }
324
325
326 Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
327 if (authentication != null)
328 {
329
330 if (authentication instanceof Authentication.User &&
331 _loginService!=null &&
332 !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
333 {
334 LOG.debug("auth revoked {}",authentication);
335 session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
336 }
337 else
338 {
339 synchronized (session)
340 {
341 String j_uri=(String)session.getAttribute(__J_URI);
342 if (j_uri!=null)
343 {
344
345
346 LOG.debug("auth retry {}->{}",authentication,j_uri);
347 StringBuffer buf = request.getRequestURL();
348 if (request.getQueryString() != null)
349 buf.append("?").append(request.getQueryString());
350
351 if (j_uri.equals(buf.toString()))
352 {
353 MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
354 if (j_post!=null)
355 {
356 LOG.debug("auth rePOST {}->{}",authentication,j_uri);
357 base_request.setContentParameters(j_post);
358 }
359 session.removeAttribute(__J_URI);
360 session.removeAttribute(__J_METHOD);
361 session.removeAttribute(__J_POST);
362 }
363 }
364 }
365 LOG.debug("auth {}",authentication);
366 return authentication;
367 }
368 }
369
370
371 if (DeferredAuthentication.isDeferred(response))
372 {
373 LOG.debug("auth deferred {}",session.getId());
374 return Authentication.UNAUTHENTICATED;
375 }
376
377
378 synchronized (session)
379 {
380
381 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
382 {
383 StringBuffer buf = request.getRequestURL();
384 if (request.getQueryString() != null)
385 buf.append("?").append(request.getQueryString());
386 session.setAttribute(__J_URI, buf.toString());
387 session.setAttribute(__J_METHOD, request.getMethod());
388
389 if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
390 {
391 MultiMap<String> formParameters = new MultiMap<>();
392 base_request.extractFormParameters(formParameters);
393 session.setAttribute(__J_POST, formParameters);
394 }
395 }
396 }
397
398
399 if (_dispatch)
400 {
401 LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
402 RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
403 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
404 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
405 dispatcher.forward(new FormRequest(request), new FormResponse(response));
406 }
407 else
408 {
409 LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
410 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
411 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
412 }
413 return Authentication.SEND_CONTINUE;
414 }
415 catch (IOException | ServletException e)
416 {
417 throw new ServerAuthException(e);
418 }
419 }
420
421
422 public boolean isJSecurityCheck(String uri)
423 {
424 int jsc = uri.indexOf(__J_SECURITY_CHECK);
425
426 if (jsc<0)
427 return false;
428 int e=jsc+__J_SECURITY_CHECK.length();
429 if (e==uri.length())
430 return true;
431 char c = uri.charAt(e);
432 return c==';'||c=='#'||c=='/'||c=='?';
433 }
434
435
436 public boolean isLoginOrErrorPage(String pathInContext)
437 {
438 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
439 }
440
441
442 @Override
443 public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
444 {
445 return true;
446 }
447
448
449
450 protected static class FormRequest extends HttpServletRequestWrapper
451 {
452 public FormRequest(HttpServletRequest request)
453 {
454 super(request);
455 }
456
457 @Override
458 public long getDateHeader(String name)
459 {
460 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
461 return -1;
462 return super.getDateHeader(name);
463 }
464
465 @Override
466 public String getHeader(String name)
467 {
468 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
469 return null;
470 return super.getHeader(name);
471 }
472
473 @Override
474 public Enumeration<String> getHeaderNames()
475 {
476 return Collections.enumeration(Collections.list(super.getHeaderNames()));
477 }
478
479 @Override
480 public Enumeration<String> getHeaders(String name)
481 {
482 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
483 return Collections.<String>enumeration(Collections.<String>emptyList());
484 return super.getHeaders(name);
485 }
486 }
487
488
489
490 protected static class FormResponse extends HttpServletResponseWrapper
491 {
492 public FormResponse(HttpServletResponse response)
493 {
494 super(response);
495 }
496
497 @Override
498 public void addDateHeader(String name, long date)
499 {
500 if (notIgnored(name))
501 super.addDateHeader(name,date);
502 }
503
504 @Override
505 public void addHeader(String name, String value)
506 {
507 if (notIgnored(name))
508 super.addHeader(name,value);
509 }
510
511 @Override
512 public void setDateHeader(String name, long date)
513 {
514 if (notIgnored(name))
515 super.setDateHeader(name,date);
516 }
517
518 @Override
519 public void setHeader(String name, String value)
520 {
521 if (notIgnored(name))
522 super.setHeader(name,value);
523 }
524
525 private boolean notIgnored(String name)
526 {
527 if (HttpHeader.CACHE_CONTROL.is(name) ||
528 HttpHeader.PRAGMA.is(name) ||
529 HttpHeader.ETAG.is(name) ||
530 HttpHeader.EXPIRES.is(name) ||
531 HttpHeader.LAST_MODIFIED.is(name) ||
532 HttpHeader.AGE.is(name))
533 return false;
534 return true;
535 }
536 }
537
538
539
540
541
542
543 public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
544 {
545 public FormAuthentication(String method, UserIdentity userIdentity)
546 {
547 super(method,userIdentity);
548 }
549
550 @Override
551 public String toString()
552 {
553 return "Form"+super.toString();
554 }
555 }
556 }