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 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
57
58
59
60
61
62
63
64
65
66
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
109
110
111
112
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
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
210
211
212
213
214
215
216 HttpServletRequest httpRequest = (HttpServletRequest)request;
217 HttpSession session = httpRequest.getSession(false);
218 if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
219 return;
220
221 String juri = (String)session.getAttribute(__J_URI);
222 if (juri == null || juri.length() == 0)
223 return;
224
225 String method = (String)session.getAttribute(__J_METHOD);
226 if (method == null || method.length() == 0)
227 return;
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;
235
236
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
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 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
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
330 Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
331 if (authentication != null)
332 {
333
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
349
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
376 if (DeferredAuthentication.isDeferred(response))
377 {
378 LOG.debug("auth deferred {}",session.getId());
379 return Authentication.UNAUTHENTICATED;
380 }
381
382
383 synchronized (session)
384 {
385
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
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
548
549
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 }