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.HttpChannel;
46 import org.eclipse.jetty.server.Request;
47 import org.eclipse.jetty.server.Response;
48 import org.eclipse.jetty.server.UserIdentity;
49 import org.eclipse.jetty.util.MultiMap;
50 import org.eclipse.jetty.util.StringUtil;
51 import org.eclipse.jetty.util.URIUtil;
52 import org.eclipse.jetty.util.log.Log;
53 import org.eclipse.jetty.util.log.Logger;
54 import org.eclipse.jetty.util.security.Constraint;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class FormAuthenticator extends LoginAuthenticator
72 {
73 private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
74
75 public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
76 public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
77 public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
78 public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
79 public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
80 public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
81 public final static String __J_SECURITY_CHECK = "/j_security_check";
82 public final static String __J_USERNAME = "j_username";
83 public final static String __J_PASSWORD = "j_password";
84
85 private String _formErrorPage;
86 private String _formErrorPath;
87 private String _formLoginPage;
88 private String _formLoginPath;
89 private boolean _dispatch;
90 private boolean _alwaysSaveUri;
91
92 public FormAuthenticator()
93 {
94 }
95
96
97 public FormAuthenticator(String login,String error,boolean dispatch)
98 {
99 this();
100 if (login!=null)
101 setLoginPage(login);
102 if (error!=null)
103 setErrorPage(error);
104 _dispatch=dispatch;
105 }
106
107
108
109
110
111
112
113
114
115 public void setAlwaysSaveUri (boolean alwaysSave)
116 {
117 _alwaysSaveUri = alwaysSave;
118 }
119
120
121
122 public boolean getAlwaysSaveUri ()
123 {
124 return _alwaysSaveUri;
125 }
126
127
128
129
130
131 @Override
132 public void setConfiguration(AuthConfiguration configuration)
133 {
134 super.setConfiguration(configuration);
135 String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
136 if (login!=null)
137 setLoginPage(login);
138 String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
139 if (error!=null)
140 setErrorPage(error);
141 String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
142 _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
143 }
144
145
146 @Override
147 public String getAuthMethod()
148 {
149 return Constraint.__FORM_AUTH;
150 }
151
152
153 private void setLoginPage(String path)
154 {
155 if (!path.startsWith("/"))
156 {
157 LOG.warn("form-login-page must start with /");
158 path = "/" + path;
159 }
160 _formLoginPage = path;
161 _formLoginPath = path;
162 if (_formLoginPath.indexOf('?') > 0)
163 _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
164 }
165
166
167 private void setErrorPage(String path)
168 {
169 if (path == null || path.trim().length() == 0)
170 {
171 _formErrorPath = null;
172 _formErrorPage = null;
173 }
174 else
175 {
176 if (!path.startsWith("/"))
177 {
178 LOG.warn("form-error-page must start with /");
179 path = "/" + path;
180 }
181 _formErrorPage = path;
182 _formErrorPath = path;
183
184 if (_formErrorPath.indexOf('?') > 0)
185 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
186 }
187 }
188
189
190
191 @Override
192 public UserIdentity login(String username, Object password, ServletRequest request)
193 {
194
195 UserIdentity user = super.login(username,password,request);
196 if (user!=null)
197 {
198 HttpSession session = ((HttpServletRequest)request).getSession(true);
199 Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
200 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
201 }
202 return user;
203 }
204
205
206
207 @Override
208 public void prepareRequest(ServletRequest request)
209 {
210
211
212
213
214
215
216
217 HttpServletRequest httpRequest = (HttpServletRequest)request;
218 HttpSession session = httpRequest.getSession(false);
219 if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
220 return;
221
222 String juri = (String)session.getAttribute(__J_URI);
223 if (juri == null || juri.length() == 0)
224 return;
225
226 String method = (String)session.getAttribute(__J_METHOD);
227 if (method == null || method.length() == 0)
228 return;
229
230 StringBuffer buf = httpRequest.getRequestURL();
231 if (httpRequest.getQueryString() != null)
232 buf.append("?").append(httpRequest.getQueryString());
233
234 if (!juri.equals(buf.toString()))
235 return;
236
237
238 if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
239 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
240 HttpMethod m = HttpMethod.fromString(method);
241 base_request.setMethod(m,m.asString());
242 }
243
244
245 @Override
246 public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
247 {
248 HttpServletRequest request = (HttpServletRequest)req;
249 HttpServletResponse response = (HttpServletResponse)res;
250 String uri = request.getRequestURI();
251 if (uri==null)
252 uri=URIUtil.SLASH;
253
254 mandatory|=isJSecurityCheck(uri);
255 if (!mandatory)
256 return new DeferredAuthentication(this);
257
258 if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
259 return new DeferredAuthentication(this);
260
261 HttpSession session = request.getSession(true);
262
263 try
264 {
265
266 if (isJSecurityCheck(uri))
267 {
268 final String username = request.getParameter(__J_USERNAME);
269 final String password = request.getParameter(__J_PASSWORD);
270
271 UserIdentity user = login(username, password, request);
272 LOG.debug("jsecuritycheck {} {}",username,user);
273 session = request.getSession(true);
274 if (user!=null)
275 {
276
277 String nuri;
278 FormAuthentication form_auth;
279 synchronized(session)
280 {
281 nuri = (String) session.getAttribute(__J_URI);
282
283 if (nuri == null || nuri.length() == 0)
284 {
285 nuri = request.getContextPath();
286 if (nuri.length() == 0)
287 nuri = URIUtil.SLASH;
288 }
289 form_auth = new FormAuthentication(getAuthMethod(),user);
290 }
291 LOG.debug("authenticated {}->{}",form_auth,nuri);
292
293 response.setContentLength(0);
294 Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
295 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
296 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
297 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
298 return form_auth;
299 }
300
301
302 if (LOG.isDebugEnabled())
303 LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
304 if (_formErrorPage == null)
305 {
306 LOG.debug("auth failed {}->403",username);
307 if (response != null)
308 response.sendError(HttpServletResponse.SC_FORBIDDEN);
309 }
310 else if (_dispatch)
311 {
312 LOG.debug("auth failed {}=={}",username,_formErrorPage);
313 RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
314 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
315 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
316 dispatcher.forward(new FormRequest(request), new FormResponse(response));
317 }
318 else
319 {
320 LOG.debug("auth failed {}->{}",username,_formErrorPage);
321 Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
322 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
323 int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
324 base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
325 }
326
327 return Authentication.SEND_FAILURE;
328 }
329
330
331 Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
332 if (authentication != null)
333 {
334
335 if (authentication instanceof Authentication.User &&
336 _loginService!=null &&
337 !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
338 {
339 LOG.debug("auth revoked {}",authentication);
340 session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
341 }
342 else
343 {
344 synchronized (session)
345 {
346 String j_uri=(String)session.getAttribute(__J_URI);
347 if (j_uri!=null)
348 {
349
350
351 LOG.debug("auth retry {}->{}",authentication,j_uri);
352 StringBuffer buf = request.getRequestURL();
353 if (request.getQueryString() != null)
354 buf.append("?").append(request.getQueryString());
355
356 if (j_uri.equals(buf.toString()))
357 {
358 MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
359 if (j_post!=null)
360 {
361 LOG.debug("auth rePOST {}->{}",authentication,j_uri);
362 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
363 base_request.setParameters(j_post);
364 }
365 session.removeAttribute(__J_URI);
366 session.removeAttribute(__J_METHOD);
367 session.removeAttribute(__J_POST);
368 }
369 }
370 }
371 LOG.debug("auth {}",authentication);
372 return authentication;
373 }
374 }
375
376
377 if (DeferredAuthentication.isDeferred(response))
378 {
379 LOG.debug("auth deferred {}",session.getId());
380 return Authentication.UNAUTHENTICATED;
381 }
382
383
384 synchronized (session)
385 {
386
387 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
388 {
389 StringBuffer buf = request.getRequestURL();
390 if (request.getQueryString() != null)
391 buf.append("?").append(request.getQueryString());
392 session.setAttribute(__J_URI, buf.toString());
393 session.setAttribute(__J_METHOD, request.getMethod());
394
395 if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
396 {
397 Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
398 base_request.extractParameters();
399 session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
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 }