2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.security;
21 import java.io.IOException;
22 import java.security.Principal;
23 import java.util.Collection;
24 import java.util.Enumeration;
25 import java.util.HashMap;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32 import javax.servlet.http.HttpSessionEvent;
33 import javax.servlet.http.HttpSessionListener;
35 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
36 import org.eclipse.jetty.server.Authentication;
37 import org.eclipse.jetty.server.Handler;
38 import org.eclipse.jetty.server.HttpChannel;
39 import org.eclipse.jetty.server.Request;
40 import org.eclipse.jetty.server.Response;
41 import org.eclipse.jetty.server.UserIdentity;
42 import org.eclipse.jetty.server.handler.ContextHandler;
43 import org.eclipse.jetty.server.handler.ContextHandler.Context;
44 import org.eclipse.jetty.server.handler.HandlerWrapper;
45 import org.eclipse.jetty.server.session.AbstractSession;
46 import org.eclipse.jetty.util.component.LifeCycle;
47 import org.eclipse.jetty.util.log.Log;
48 import org.eclipse.jetty.util.log.Logger;
51 * Abstract SecurityHandler.
52 * Select and apply an {@link Authenticator} to a request.
54 * The Authenticator may either be directly set on the handler
55 * or will be create during {@link #start()} with a call to
56 * either the default or set AuthenticatorFactory.
58 * SecurityHandler has a set of initparameters that are used by the
59 * Authentication.Configuration. At startup, any context init parameters
60 * that start with "org.eclipse.jetty.security." that do not have
61 * values in the SecurityHandler init parameters, are copied.
64 public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
66 private static final Logger LOG = Log.getLogger(SecurityHandler.class);
68 /* ------------------------------------------------------------ */
69 private boolean _checkWelcomeFiles = false;
70 private Authenticator _authenticator;
71 private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
72 private String _realmName;
73 private String _authMethod;
74 private final Map<String,String> _initParameters=new HashMap<String,String>();
75 private LoginService _loginService;
76 private IdentityService _identityService;
77 private boolean _renewSession=true;
79 /* ------------------------------------------------------------ */
80 protected SecurityHandler()
82 addBean(_authenticatorFactory);
85 /* ------------------------------------------------------------ */
86 /** Get the identityService.
87 * @return the identityService
90 public IdentityService getIdentityService()
92 return _identityService;
95 /* ------------------------------------------------------------ */
96 /** Set the identityService.
97 * @param identityService the identityService to set
99 public void setIdentityService(IdentityService identityService)
102 throw new IllegalStateException("Started");
103 updateBean(_identityService,identityService);
104 _identityService = identityService;
107 /* ------------------------------------------------------------ */
108 /** Get the loginService.
109 * @return the loginService
112 public LoginService getLoginService()
114 return _loginService;
117 /* ------------------------------------------------------------ */
118 /** Set the loginService.
119 * @param loginService the loginService to set
121 public void setLoginService(LoginService loginService)
124 throw new IllegalStateException("Started");
125 updateBean(_loginService,loginService);
126 _loginService = loginService;
130 /* ------------------------------------------------------------ */
131 public Authenticator getAuthenticator()
133 return _authenticator;
136 /* ------------------------------------------------------------ */
137 /** Set the authenticator.
138 * @param authenticator
139 * @throws IllegalStateException if the SecurityHandler is running
141 public void setAuthenticator(Authenticator authenticator)
144 throw new IllegalStateException("Started");
145 updateBean(_authenticator,authenticator);
146 _authenticator = authenticator;
147 if (_authenticator!=null)
148 _authMethod=_authenticator.getAuthMethod();
151 /* ------------------------------------------------------------ */
153 * @return the authenticatorFactory
155 public Authenticator.Factory getAuthenticatorFactory()
157 return _authenticatorFactory;
160 /* ------------------------------------------------------------ */
162 * @param authenticatorFactory the authenticatorFactory to set
163 * @throws IllegalStateException if the SecurityHandler is running
165 public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
168 throw new IllegalStateException("running");
169 updateBean(_authenticatorFactory,authenticatorFactory);
170 _authenticatorFactory = authenticatorFactory;
173 /* ------------------------------------------------------------ */
175 * @return the realmName
178 public String getRealmName()
183 /* ------------------------------------------------------------ */
185 * @param realmName the realmName to set
186 * @throws IllegalStateException if the SecurityHandler is running
188 public void setRealmName(String realmName)
191 throw new IllegalStateException("running");
192 _realmName = realmName;
195 /* ------------------------------------------------------------ */
197 * @return the authMethod
200 public String getAuthMethod()
205 /* ------------------------------------------------------------ */
207 * @param authMethod the authMethod to set
208 * @throws IllegalStateException if the SecurityHandler is running
210 public void setAuthMethod(String authMethod)
213 throw new IllegalStateException("running");
214 _authMethod = authMethod;
217 /* ------------------------------------------------------------ */
219 * @return True if forwards to welcome files are authenticated
221 public boolean isCheckWelcomeFiles()
223 return _checkWelcomeFiles;
226 /* ------------------------------------------------------------ */
228 * @param authenticateWelcomeFiles True if forwards to welcome files are
230 * @throws IllegalStateException if the SecurityHandler is running
232 public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
235 throw new IllegalStateException("running");
236 _checkWelcomeFiles = authenticateWelcomeFiles;
239 /* ------------------------------------------------------------ */
241 public String getInitParameter(String key)
243 return _initParameters.get(key);
246 /* ------------------------------------------------------------ */
248 public Set<String> getInitParameterNames()
250 return _initParameters.keySet();
253 /* ------------------------------------------------------------ */
254 /** Set an initialization parameter.
257 * @return previous value
258 * @throws IllegalStateException if the SecurityHandler is running
260 public String setInitParameter(String key, String value)
263 throw new IllegalStateException("running");
264 return _initParameters.put(key,value);
267 /* ------------------------------------------------------------ */
268 protected LoginService findLoginService() throws Exception
270 Collection<LoginService> list = getServer().getBeans(LoginService.class);
271 LoginService service = null;
272 String realm=getRealmName();
275 for (LoginService s : list)
276 if (s.getName()!=null && s.getName().equals(realm))
282 else if (list.size()==1)
283 service = list.iterator().next();
288 /* ------------------------------------------------------------ */
289 protected IdentityService findIdentityService()
291 return getServer().getBean(IdentityService.class);
294 /* ------------------------------------------------------------ */
298 protected void doStart()
301 // copy security init parameters
302 ContextHandler.Context context =ContextHandler.getCurrentContext();
305 Enumeration<String> names=context.getInitParameterNames();
306 while (names!=null && names.hasMoreElements())
308 String name =names.nextElement();
309 if (name.startsWith("org.eclipse.jetty.security.") &&
310 getInitParameter(name)==null)
311 setInitParameter(name,context.getInitParameter(name));
314 //register a session listener to handle securing sessions when authentication is performed
315 context.getContextHandler().addEventListener(new HttpSessionListener()
318 public void sessionDestroyed(HttpSessionEvent se)
323 public void sessionCreated(HttpSessionEvent se)
325 //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
326 HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();
330 Request request = channel.getRequest();
334 if (request.isSecure())
336 se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
342 // complicated resolution of login and identity service to handle
343 // many different ways these can be constructed and injected.
345 if (_loginService==null)
347 setLoginService(findLoginService());
348 if (_loginService!=null)
349 unmanage(_loginService);
352 if (_identityService==null)
354 if (_loginService!=null)
355 setIdentityService(_loginService.getIdentityService());
357 if (_identityService==null)
358 setIdentityService(findIdentityService());
360 if (_identityService==null)
362 if (_realmName!=null)
364 setIdentityService(new DefaultIdentityService());
365 manage(_identityService);
369 unmanage(_identityService);
372 if (_loginService!=null)
374 if (_loginService.getIdentityService()==null)
375 _loginService.setIdentityService(_identityService);
376 else if (_loginService.getIdentityService()!=_identityService)
377 throw new IllegalStateException("LoginService has different IdentityService to "+this);
380 Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
381 if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
382 setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
384 if (_authenticator!=null)
385 _authenticator.setConfiguration(this);
386 else if (_realmName!=null)
388 LOG.warn("No Authenticator for "+this);
389 throw new IllegalStateException("No Authenticator");
396 /* ------------------------------------------------------------ */
397 protected void doStop() throws Exception
399 //if we discovered the services (rather than had them explicitly configured), remove them.
400 if (!isManaged(_identityService))
402 removeBean(_identityService);
403 _identityService = null;
406 if (!isManaged(_loginService))
408 removeBean(_loginService);
415 /* ------------------------------------------------------------ */
416 protected boolean checkSecurity(Request request)
418 switch(request.getDispatcherType())
424 if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
426 request.removeAttribute("org.eclipse.jetty.server.welcome");
435 /* ------------------------------------------------------------ */
437 * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
440 public boolean isSessionRenewedOnAuthentication()
442 return _renewSession;
445 /* ------------------------------------------------------------ */
446 /** Set renew the session on Authentication.
448 * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
449 * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
451 public void setSessionRenewedOnAuthentication(boolean renew)
456 /* ------------------------------------------------------------ */
458 * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
459 * javax.servlet.http.HttpServletRequest,
460 * javax.servlet.http.HttpServletResponse, int)
463 public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
465 final Response base_response = baseRequest.getResponse();
466 final Handler handler=getHandler();
471 final Authenticator authenticator = _authenticator;
473 if (checkSecurity(baseRequest))
475 //See Servlet Spec 3.1 sec 13.6.3
476 if (authenticator != null)
477 authenticator.prepareRequest(baseRequest);
479 RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
481 // Check data constraints
482 if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
484 if (!baseRequest.isHandled())
486 response.sendError(HttpServletResponse.SC_FORBIDDEN);
487 baseRequest.setHandled(true);
492 // is Auth mandatory?
493 boolean isAuthMandatory =
494 isAuthMandatory(baseRequest, base_response, roleInfo);
496 if (isAuthMandatory && authenticator==null)
498 LOG.warn("No authenticator for: "+roleInfo);
499 if (!baseRequest.isHandled())
501 response.sendError(HttpServletResponse.SC_FORBIDDEN);
502 baseRequest.setHandled(true);
507 // check authentication
508 Object previousIdentity = null;
511 Authentication authentication = baseRequest.getAuthentication();
512 if (authentication==null || authentication==Authentication.NOT_CHECKED)
513 authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
515 if (authentication instanceof Authentication.Wrapped)
517 request=((Authentication.Wrapped)authentication).getHttpServletRequest();
518 response=((Authentication.Wrapped)authentication).getHttpServletResponse();
521 if (authentication instanceof Authentication.ResponseSent)
523 baseRequest.setHandled(true);
525 else if (authentication instanceof Authentication.User)
527 Authentication.User userAuth = (Authentication.User)authentication;
528 baseRequest.setAuthentication(authentication);
529 if (_identityService!=null)
530 previousIdentity = _identityService.associate(userAuth.getUserIdentity());
534 boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
537 response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
538 baseRequest.setHandled(true);
543 handler.handle(pathInContext, baseRequest, request, response);
544 if (authenticator!=null)
545 authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
547 else if (authentication instanceof Authentication.Deferred)
549 DeferredAuthentication deferred= (DeferredAuthentication)authentication;
550 baseRequest.setAuthentication(authentication);
554 handler.handle(pathInContext, baseRequest, request, response);
558 previousIdentity = deferred.getPreviousAssociation();
561 if (authenticator!=null)
563 Authentication auth=baseRequest.getAuthentication();
564 if (auth instanceof Authentication.User)
566 Authentication.User userAuth = (Authentication.User)auth;
567 authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
570 authenticator.secureResponse(request, response, isAuthMandatory, null);
575 baseRequest.setAuthentication(authentication);
576 if (_identityService!=null)
577 previousIdentity = _identityService.associate(null);
578 handler.handle(pathInContext, baseRequest, request, response);
579 if (authenticator!=null)
580 authenticator.secureResponse(request, response, isAuthMandatory, null);
583 catch (ServerAuthException e)
585 // jaspi 3.8.3 send HTTP 500 internal server error, with message
586 // from AuthException
587 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
591 if (_identityService!=null)
592 _identityService.disassociate(previousIdentity);
596 handler.handle(pathInContext, baseRequest, request, response);
600 /* ------------------------------------------------------------ */
601 public static SecurityHandler getCurrentSecurityHandler()
603 Context context = ContextHandler.getCurrentContext();
607 return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
610 /* ------------------------------------------------------------ */
611 public void logout(Authentication.User user)
613 LOG.debug("logout {}",user);
614 LoginService login_service=getLoginService();
615 if (login_service!=null)
617 login_service.logout(user.getUserIdentity());
620 IdentityService identity_service=getIdentityService();
621 if (identity_service!=null)
623 // TODO recover previous from threadlocal (or similar)
624 Object previous=null;
625 identity_service.disassociate(previous);
629 /* ------------------------------------------------------------ */
630 protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
632 /* ------------------------------------------------------------ */
633 protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
635 /* ------------------------------------------------------------ */
636 protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
638 /* ------------------------------------------------------------ */
639 protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
640 UserIdentity userIdentity) throws IOException;
643 /* ------------------------------------------------------------ */
644 /* ------------------------------------------------------------ */
645 public class NotChecked implements Principal
648 public String getName()
654 public String toString()
656 return "NOT CHECKED";
659 public SecurityHandler getSecurityHandler()
661 return SecurityHandler.this;
666 /* ------------------------------------------------------------ */
667 /* ------------------------------------------------------------ */
668 public static final Principal __NO_USER = new Principal()
671 public String getName()
677 public String toString()
683 /* ------------------------------------------------------------ */
684 /* ------------------------------------------------------------ */
686 * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
687 * of authentication. A request with a Nobody UserPrincipal will be allowed
688 * past all authentication constraints - but will not be considered an
689 * authenticated request. It can be used by Authenticators such as
690 * FormAuthenticator to allow access to logon and error pages within an
691 * authenticated URI tree.
693 public static final Principal __NOBODY = new Principal()
696 public String getName()
702 public String toString()