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.MimeTypes;
39 import org.eclipse.jetty.security.ServerAuthException;
40 import org.eclipse.jetty.security.UserAuthentication;
41 import org.eclipse.jetty.server.Authentication;
42 import org.eclipse.jetty.server.Authentication.User;
43 import org.eclipse.jetty.server.HttpChannel;
44 import org.eclipse.jetty.server.Request;
45 import org.eclipse.jetty.server.UserIdentity;
46 import org.eclipse.jetty.util.MultiMap;
47 import org.eclipse.jetty.util.StringUtil;
48 import org.eclipse.jetty.util.URIUtil;
49 import org.eclipse.jetty.util.log.Log;
50 import org.eclipse.jetty.util.log.Logger;
51 import org.eclipse.jetty.util.security.Constraint;
52
53
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_SECURITY_CHECK = "/j_security_check";
78 public final static String __J_USERNAME = "j_username";
79 public final static String __J_PASSWORD = "j_password";
80
81 private String _formErrorPage;
82 private String _formErrorPath;
83 private String _formLoginPage;
84 private String _formLoginPath;
85 private boolean _dispatch;
86 private boolean _alwaysSaveUri;
87
88 public FormAuthenticator()
89 {
90 }
91
92
93 public FormAuthenticator(String login,String error,boolean dispatch)
94 {
95 this();
96 if (login!=null)
97 setLoginPage(login);
98 if (error!=null)
99 setErrorPage(error);
100 _dispatch=dispatch;
101 }
102
103
104
105
106
107
108
109
110
111 public void setAlwaysSaveUri (boolean alwaysSave)
112 {
113 _alwaysSaveUri = alwaysSave;
114 }
115
116
117
118 public boolean getAlwaysSaveUri ()
119 {
120 return _alwaysSaveUri;
121 }
122
123
124
125
126
127 @Override
128 public void setConfiguration(AuthConfiguration configuration)
129 {
130 super.setConfiguration(configuration);
131 String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
132 if (login!=null)
133 setLoginPage(login);
134 String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
135 if (error!=null)
136 setErrorPage(error);
137 String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
138 _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
139 }
140
141
142 @Override
143 public String getAuthMethod()
144 {
145 return Constraint.__FORM_AUTH;
146 }
147
148
149 private void setLoginPage(String path)
150 {
151 if (!path.startsWith("/"))
152 {
153 LOG.warn("form-login-page must start with /");
154 path = "/" + path;
155 }
156 _formLoginPage = path;
157 _formLoginPath = path;
158 if (_formLoginPath.indexOf('?') > 0)
159 _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
160 }
161
162
163 private void setErrorPage(String path)
164 {
165 if (path == null || path.trim().length() == 0)
166 {
167 _formErrorPath = null;
168 _formErrorPage = null;
169 }
170 else
171 {
172 if (!path.startsWith("/"))
173 {
174 LOG.warn("form-error-page must start with /");
175 path = "/" + path;
176 }
177 _formErrorPage = path;
178 _formErrorPath = path;
179
180 if (_formErrorPath.indexOf('?') > 0)
181 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
182 }
183 }
184
185
186
187 @Override
188 public UserIdentity login(String username, Object password, ServletRequest request)
189 {
190
191 UserIdentity user = super.login(username,password,request);
192 if (user!=null)
193 {
194 HttpSession session = ((HttpServletRequest)request).getSession(true);
195 Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
196 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
197 }
198 return user;
199 }
200
201
202 @Override
203 public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
204 {
205 HttpServletRequest request = (HttpServletRequest)req;
206 HttpServletResponse response = (HttpServletResponse)res;
207 String uri = request.getRequestURI();
208 if (uri==null)
209 uri=URIUtil.SLASH;
210
211 mandatory|=isJSecurityCheck(uri);
212 if (!mandatory)
213 return new DeferredAuthentication(this);
214
215 if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
216 return new DeferredAuthentication(this);
217
218 HttpSession session = request.getSession(true);
219
220 try
221 {
222
223 if (isJSecurityCheck(uri))
224 {
225 final String username = request.getParameter(__J_USERNAME);
226 final String password = request.getParameter(__J_PASSWORD);
227
228 UserIdentity user = login(username, password, request);
229 LOG.debug("jsecuritycheck {} {}",username,user);
230 session = request.getSession(true);
231 if (user!=null)
232 {
233
234 String nuri;
235 FormAuthentication form_auth;
236 synchronized(session)
237 {
238 nuri = (String) session.getAttribute(__J_URI);
239
240 if (nuri == null || nuri.length() == 0)
241 {
242 nuri = request.getContextPath();
243 if (nuri.length() == 0)
244 nuri = URIUtil.SLASH;
245 }
246 form_auth = new FormAuthentication(getAuthMethod(),user);
247 }
248 LOG.debug("authenticated {}->{}",form_auth,nuri);
249
250 response.setContentLength(0);
251 response.sendRedirect(response.encodeRedirectURL(nuri));
252 return form_auth;
253 }
254
255
256 if (LOG.isDebugEnabled())
257 LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
258 if (_formErrorPage == null)
259 {
260 LOG.debug("auth failed {}->403",username);
261 if (response != null)
262 response.sendError(HttpServletResponse.SC_FORBIDDEN);
263 }
264 else if (_dispatch)
265 {
266 LOG.debug("auth failed {}=={}",username,_formErrorPage);
267 RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
268 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
269 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
270 dispatcher.forward(new FormRequest(request), new FormResponse(response));
271 }
272 else
273 {
274 LOG.debug("auth failed {}->{}",username,_formErrorPage);
275 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
276 }
277
278 return Authentication.SEND_FAILURE;
279 }
280
281
282 Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
283 if (authentication != null)
284 {
285
286 if (authentication instanceof Authentication.User &&
287 _loginService!=null &&
288 !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
289 {
290 LOG.debug("auth revoked {}",authentication);
291 session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
292 }
293 else
294 {
295 synchronized (session)
296 {
297 String j_uri=(String)session.getAttribute(__J_URI);
298 if (j_uri!=null)
299 {
300 LOG.debug("auth retry {}->{}",authentication,j_uri);
301 MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
302 if (j_post!=null)
303 {
304 LOG.debug("auth rePOST {}->{}",authentication,j_uri);
305 StringBuffer buf = request.getRequestURL();
306 if (request.getQueryString() != null)
307 buf.append("?").append(request.getQueryString());
308
309 if (j_uri.equals(buf.toString()))
310 {
311
312
313
314 session.removeAttribute(__J_POST);
315 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
316 base_request.setMethod(HttpMethod.POST,HttpMethod.POST.asString());
317 base_request.setParameters(j_post);
318 }
319 }
320 else
321 session.removeAttribute(__J_URI);
322 }
323 }
324 LOG.debug("auth {}",authentication);
325 return authentication;
326 }
327 }
328
329
330 if (DeferredAuthentication.isDeferred(response))
331 {
332 LOG.debug("auth deferred {}",session.getId());
333 return Authentication.UNAUTHENTICATED;
334 }
335
336
337 synchronized (session)
338 {
339
340 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
341 {
342 StringBuffer buf = request.getRequestURL();
343 if (request.getQueryString() != null)
344 buf.append("?").append(request.getQueryString());
345 session.setAttribute(__J_URI, buf.toString());
346
347 if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
348 {
349 Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
350 base_request.extractParameters();
351 session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
352 }
353 }
354 }
355
356
357 if (_dispatch)
358 {
359 LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
360 RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
361 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
362 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
363 dispatcher.forward(new FormRequest(request), new FormResponse(response));
364 }
365 else
366 {
367 LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
368 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
369 }
370 return Authentication.SEND_CONTINUE;
371 }
372 catch (IOException | ServletException e)
373 {
374 throw new ServerAuthException(e);
375 }
376 }
377
378
379 public boolean isJSecurityCheck(String uri)
380 {
381 int jsc = uri.indexOf(__J_SECURITY_CHECK);
382
383 if (jsc<0)
384 return false;
385 int e=jsc+__J_SECURITY_CHECK.length();
386 if (e==uri.length())
387 return true;
388 char c = uri.charAt(e);
389 return c==';'||c=='#'||c=='/'||c=='?';
390 }
391
392
393 public boolean isLoginOrErrorPage(String pathInContext)
394 {
395 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
396 }
397
398
399 @Override
400 public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
401 {
402 return true;
403 }
404
405
406
407 protected static class FormRequest extends HttpServletRequestWrapper
408 {
409 public FormRequest(HttpServletRequest request)
410 {
411 super(request);
412 }
413
414 @Override
415 public long getDateHeader(String name)
416 {
417 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
418 return -1;
419 return super.getDateHeader(name);
420 }
421
422 @Override
423 public String getHeader(String name)
424 {
425 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
426 return null;
427 return super.getHeader(name);
428 }
429
430 @Override
431 public Enumeration<String> getHeaderNames()
432 {
433 return Collections.enumeration(Collections.list(super.getHeaderNames()));
434 }
435
436 @Override
437 public Enumeration<String> getHeaders(String name)
438 {
439 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
440 return Collections.<String>enumeration(Collections.<String>emptyList());
441 return super.getHeaders(name);
442 }
443 }
444
445
446
447 protected static class FormResponse extends HttpServletResponseWrapper
448 {
449 public FormResponse(HttpServletResponse response)
450 {
451 super(response);
452 }
453
454 @Override
455 public void addDateHeader(String name, long date)
456 {
457 if (notIgnored(name))
458 super.addDateHeader(name,date);
459 }
460
461 @Override
462 public void addHeader(String name, String value)
463 {
464 if (notIgnored(name))
465 super.addHeader(name,value);
466 }
467
468 @Override
469 public void setDateHeader(String name, long date)
470 {
471 if (notIgnored(name))
472 super.setDateHeader(name,date);
473 }
474
475 @Override
476 public void setHeader(String name, String value)
477 {
478 if (notIgnored(name))
479 super.setHeader(name,value);
480 }
481
482 private boolean notIgnored(String name)
483 {
484 if (HttpHeader.CACHE_CONTROL.is(name) ||
485 HttpHeader.PRAGMA.is(name) ||
486 HttpHeader.ETAG.is(name) ||
487 HttpHeader.EXPIRES.is(name) ||
488 HttpHeader.LAST_MODIFIED.is(name) ||
489 HttpHeader.AGE.is(name))
490 return false;
491 return true;
492 }
493 }
494
495
496
497
498
499
500 public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
501 {
502 public FormAuthentication(String method, UserIdentity userIdentity)
503 {
504 super(method,userIdentity);
505 }
506
507 @Override
508 public String toString()
509 {
510 return "Form"+super.toString();
511 }
512 }
513 }