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.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.UserIdentity;
47 import org.eclipse.jetty.util.MultiMap;
48 import org.eclipse.jetty.util.StringUtil;
49 import org.eclipse.jetty.util.URIUtil;
50 import org.eclipse.jetty.util.log.Log;
51 import org.eclipse.jetty.util.log.Logger;
52 import org.eclipse.jetty.util.security.Constraint;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class FormAuthenticator extends LoginAuthenticator
70 {
71 private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
72
73 public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
74 public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
75 public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
76 public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
77 public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
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 @Override
204 public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
205 {
206 HttpServletRequest request = (HttpServletRequest)req;
207 HttpServletResponse response = (HttpServletResponse)res;
208 String uri = request.getRequestURI();
209 if (uri==null)
210 uri=URIUtil.SLASH;
211
212 mandatory|=isJSecurityCheck(uri);
213 if (!mandatory)
214 return new DeferredAuthentication(this);
215
216 if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
217 return new DeferredAuthentication(this);
218
219 HttpSession session = request.getSession(true);
220
221 try
222 {
223
224 if (isJSecurityCheck(uri))
225 {
226 final String username = request.getParameter(__J_USERNAME);
227 final String password = request.getParameter(__J_PASSWORD);
228
229 UserIdentity user = login(username, password, request);
230 LOG.debug("jsecuritycheck {} {}",username,user);
231 session = request.getSession(true);
232 if (user!=null)
233 {
234
235 String nuri;
236 FormAuthentication form_auth;
237 synchronized(session)
238 {
239 nuri = (String) session.getAttribute(__J_URI);
240
241 if (nuri == null || nuri.length() == 0)
242 {
243 nuri = request.getContextPath();
244 if (nuri.length() == 0)
245 nuri = URIUtil.SLASH;
246 }
247 form_auth = new FormAuthentication(getAuthMethod(),user);
248 }
249 LOG.debug("authenticated {}->{}",form_auth,nuri);
250
251 response.setContentLength(0);
252 response.sendRedirect(response.encodeRedirectURL(nuri));
253 return form_auth;
254 }
255
256
257 if (LOG.isDebugEnabled())
258 LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
259 if (_formErrorPage == null)
260 {
261 LOG.debug("auth failed {}->403",username);
262 if (response != null)
263 response.sendError(HttpServletResponse.SC_FORBIDDEN);
264 }
265 else if (_dispatch)
266 {
267 LOG.debug("auth failed {}=={}",username,_formErrorPage);
268 RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
269 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
270 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
271 dispatcher.forward(new FormRequest(request), new FormResponse(response));
272 }
273 else
274 {
275 LOG.debug("auth failed {}->{}",username,_formErrorPage);
276 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
277 }
278
279 return Authentication.SEND_FAILURE;
280 }
281
282
283 Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
284 if (authentication != null)
285 {
286
287 if (authentication instanceof Authentication.User &&
288 _loginService!=null &&
289 !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
290 {
291 LOG.debug("auth revoked {}",authentication);
292 session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
293 }
294 else
295 {
296 synchronized (session)
297 {
298 String j_uri=(String)session.getAttribute(__J_URI);
299 if (j_uri!=null)
300 {
301 LOG.debug("auth retry {}->{}",authentication,j_uri);
302 MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
303 if (j_post!=null)
304 {
305 LOG.debug("auth rePOST {}->{}",authentication,j_uri);
306 StringBuffer buf = request.getRequestURL();
307 if (request.getQueryString() != null)
308 buf.append("?").append(request.getQueryString());
309
310 if (j_uri.equals(buf.toString()))
311 {
312
313
314
315 session.removeAttribute(__J_POST);
316 Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
317 base_request.setMethod(HttpMethod.POST,HttpMethod.POST.asString());
318 base_request.setParameters(j_post);
319 }
320 }
321 else
322 session.removeAttribute(__J_URI);
323 }
324 }
325 LOG.debug("auth {}",authentication);
326 return authentication;
327 }
328 }
329
330
331 if (DeferredAuthentication.isDeferred(response))
332 {
333 LOG.debug("auth deferred {}",session.getId());
334 return Authentication.UNAUTHENTICATED;
335 }
336
337
338 synchronized (session)
339 {
340
341 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
342 {
343 StringBuffer buf = request.getRequestURL();
344 if (request.getQueryString() != null)
345 buf.append("?").append(request.getQueryString());
346 session.setAttribute(__J_URI, buf.toString());
347
348 if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
349 {
350 Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
351 base_request.extractParameters();
352 session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
353 }
354 }
355 }
356
357
358 if (_dispatch)
359 {
360 LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
361 RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
362 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
363 response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
364 dispatcher.forward(new FormRequest(request), new FormResponse(response));
365 }
366 else
367 {
368 LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
369 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
370 }
371 return Authentication.SEND_CONTINUE;
372 }
373 catch (IOException | ServletException e)
374 {
375 throw new ServerAuthException(e);
376 }
377 }
378
379
380 public boolean isJSecurityCheck(String uri)
381 {
382 int jsc = uri.indexOf(__J_SECURITY_CHECK);
383
384 if (jsc<0)
385 return false;
386 int e=jsc+__J_SECURITY_CHECK.length();
387 if (e==uri.length())
388 return true;
389 char c = uri.charAt(e);
390 return c==';'||c=='#'||c=='/'||c=='?';
391 }
392
393
394 public boolean isLoginOrErrorPage(String pathInContext)
395 {
396 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
397 }
398
399
400 @Override
401 public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
402 {
403 return true;
404 }
405
406
407
408 protected static class FormRequest extends HttpServletRequestWrapper
409 {
410 public FormRequest(HttpServletRequest request)
411 {
412 super(request);
413 }
414
415 @Override
416 public long getDateHeader(String name)
417 {
418 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
419 return -1;
420 return super.getDateHeader(name);
421 }
422
423 @Override
424 public String getHeader(String name)
425 {
426 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
427 return null;
428 return super.getHeader(name);
429 }
430
431 @Override
432 public Enumeration<String> getHeaderNames()
433 {
434 return Collections.enumeration(Collections.list(super.getHeaderNames()));
435 }
436
437 @Override
438 public Enumeration<String> getHeaders(String name)
439 {
440 if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
441 return Collections.<String>enumeration(Collections.<String>emptyList());
442 return super.getHeaders(name);
443 }
444 }
445
446
447
448 protected static class FormResponse extends HttpServletResponseWrapper
449 {
450 public FormResponse(HttpServletResponse response)
451 {
452 super(response);
453 }
454
455 @Override
456 public void addDateHeader(String name, long date)
457 {
458 if (notIgnored(name))
459 super.addDateHeader(name,date);
460 }
461
462 @Override
463 public void addHeader(String name, String value)
464 {
465 if (notIgnored(name))
466 super.addHeader(name,value);
467 }
468
469 @Override
470 public void setDateHeader(String name, long date)
471 {
472 if (notIgnored(name))
473 super.setDateHeader(name,date);
474 }
475
476 @Override
477 public void setHeader(String name, String value)
478 {
479 if (notIgnored(name))
480 super.setHeader(name,value);
481 }
482
483 private boolean notIgnored(String name)
484 {
485 if (HttpHeader.CACHE_CONTROL.is(name) ||
486 HttpHeader.PRAGMA.is(name) ||
487 HttpHeader.ETAG.is(name) ||
488 HttpHeader.EXPIRES.is(name) ||
489 HttpHeader.LAST_MODIFIED.is(name) ||
490 HttpHeader.AGE.is(name))
491 return false;
492 return true;
493 }
494 }
495
496
497
498
499
500
501 public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
502 {
503 public FormAuthentication(String method, UserIdentity userIdentity)
504 {
505 super(method,userIdentity);
506 }
507
508 @Override
509 public String toString()
510 {
511 return "Form"+super.toString();
512 }
513 }
514 }