1 // 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. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.security; 20 21 import java.io.IOException; 22 import java.security.Principal; 23 import java.util.Enumeration; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Set; 28 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; 34 35 import org.eclipse.jetty.security.authentication.DeferredAuthentication; 36 import org.eclipse.jetty.server.AbstractHttpConnection; 37 import org.eclipse.jetty.server.Authentication; 38 import org.eclipse.jetty.server.Handler; 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.AbstractSessionManager; 46 import org.eclipse.jetty.util.component.LifeCycle; 47 import org.eclipse.jetty.util.log.Log; 48 import org.eclipse.jetty.util.log.Logger; 49 50 /** 51 * Abstract SecurityHandler. 52 * Select and apply an {@link Authenticator} to a request. 53 * <p> 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. 57 * <p> 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. 62 * 63 */ 64 public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration 65 { 66 private static final Logger LOG = Log.getLogger(SecurityHandler.class); 67 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 boolean _loginServiceShared; 77 private IdentityService _identityService; 78 private boolean _renewSession=true; 79 80 /* ------------------------------------------------------------ */ SecurityHandler()81 protected SecurityHandler() 82 { 83 } 84 85 /* ------------------------------------------------------------ */ 86 /** Get the identityService. 87 * @return the identityService 88 */ getIdentityService()89 public IdentityService getIdentityService() 90 { 91 return _identityService; 92 } 93 94 /* ------------------------------------------------------------ */ 95 /** Set the identityService. 96 * @param identityService the identityService to set 97 */ setIdentityService(IdentityService identityService)98 public void setIdentityService(IdentityService identityService) 99 { 100 if (isStarted()) 101 throw new IllegalStateException("Started"); 102 _identityService = identityService; 103 } 104 105 /* ------------------------------------------------------------ */ 106 /** Get the loginService. 107 * @return the loginService 108 */ getLoginService()109 public LoginService getLoginService() 110 { 111 return _loginService; 112 } 113 114 /* ------------------------------------------------------------ */ 115 /** Set the loginService. 116 * @param loginService the loginService to set 117 */ setLoginService(LoginService loginService)118 public void setLoginService(LoginService loginService) 119 { 120 if (isStarted()) 121 throw new IllegalStateException("Started"); 122 _loginService = loginService; 123 _loginServiceShared=false; 124 } 125 126 127 /* ------------------------------------------------------------ */ getAuthenticator()128 public Authenticator getAuthenticator() 129 { 130 return _authenticator; 131 } 132 133 /* ------------------------------------------------------------ */ 134 /** Set the authenticator. 135 * @param authenticator 136 * @throws IllegalStateException if the SecurityHandler is running 137 */ setAuthenticator(Authenticator authenticator)138 public void setAuthenticator(Authenticator authenticator) 139 { 140 if (isStarted()) 141 throw new IllegalStateException("Started"); 142 _authenticator = authenticator; 143 } 144 145 /* ------------------------------------------------------------ */ 146 /** 147 * @return the authenticatorFactory 148 */ getAuthenticatorFactory()149 public Authenticator.Factory getAuthenticatorFactory() 150 { 151 return _authenticatorFactory; 152 } 153 154 /* ------------------------------------------------------------ */ 155 /** 156 * @param authenticatorFactory the authenticatorFactory to set 157 * @throws IllegalStateException if the SecurityHandler is running 158 */ setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)159 public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory) 160 { 161 if (isRunning()) 162 throw new IllegalStateException("running"); 163 _authenticatorFactory = authenticatorFactory; 164 } 165 166 /* ------------------------------------------------------------ */ 167 /** 168 * @return the realmName 169 */ getRealmName()170 public String getRealmName() 171 { 172 return _realmName; 173 } 174 175 /* ------------------------------------------------------------ */ 176 /** 177 * @param realmName the realmName to set 178 * @throws IllegalStateException if the SecurityHandler is running 179 */ setRealmName(String realmName)180 public void setRealmName(String realmName) 181 { 182 if (isRunning()) 183 throw new IllegalStateException("running"); 184 _realmName = realmName; 185 } 186 187 /* ------------------------------------------------------------ */ 188 /** 189 * @return the authMethod 190 */ getAuthMethod()191 public String getAuthMethod() 192 { 193 return _authMethod; 194 } 195 196 /* ------------------------------------------------------------ */ 197 /** 198 * @param authMethod the authMethod to set 199 * @throws IllegalStateException if the SecurityHandler is running 200 */ setAuthMethod(String authMethod)201 public void setAuthMethod(String authMethod) 202 { 203 if (isRunning()) 204 throw new IllegalStateException("running"); 205 _authMethod = authMethod; 206 } 207 208 /* ------------------------------------------------------------ */ 209 /** 210 * @return True if forwards to welcome files are authenticated 211 */ isCheckWelcomeFiles()212 public boolean isCheckWelcomeFiles() 213 { 214 return _checkWelcomeFiles; 215 } 216 217 /* ------------------------------------------------------------ */ 218 /** 219 * @param authenticateWelcomeFiles True if forwards to welcome files are 220 * authenticated 221 * @throws IllegalStateException if the SecurityHandler is running 222 */ setCheckWelcomeFiles(boolean authenticateWelcomeFiles)223 public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles) 224 { 225 if (isRunning()) 226 throw new IllegalStateException("running"); 227 _checkWelcomeFiles = authenticateWelcomeFiles; 228 } 229 230 /* ------------------------------------------------------------ */ getInitParameter(String key)231 public String getInitParameter(String key) 232 { 233 return _initParameters.get(key); 234 } 235 236 /* ------------------------------------------------------------ */ getInitParameterNames()237 public Set<String> getInitParameterNames() 238 { 239 return _initParameters.keySet(); 240 } 241 242 /* ------------------------------------------------------------ */ 243 /** Set an initialization parameter. 244 * @param key 245 * @param value 246 * @return previous value 247 * @throws IllegalStateException if the SecurityHandler is running 248 */ setInitParameter(String key, String value)249 public String setInitParameter(String key, String value) 250 { 251 if (isRunning()) 252 throw new IllegalStateException("running"); 253 return _initParameters.put(key,value); 254 } 255 256 /* ------------------------------------------------------------ */ findLoginService()257 protected LoginService findLoginService() 258 { 259 List<LoginService> list = getServer().getBeans(LoginService.class); 260 261 String realm=getRealmName(); 262 if (realm!=null) 263 { 264 for (LoginService service : list) 265 if (service.getName()!=null && service.getName().equals(realm)) 266 return service; 267 } 268 else if (list.size()==1) 269 return list.get(0); 270 return null; 271 } 272 273 /* ------------------------------------------------------------ */ findIdentityService()274 protected IdentityService findIdentityService() 275 { 276 return getServer().getBean(IdentityService.class); 277 } 278 279 /* ------------------------------------------------------------ */ 280 /** 281 */ 282 @Override doStart()283 protected void doStart() 284 throws Exception 285 { 286 // copy security init parameters 287 ContextHandler.Context context =ContextHandler.getCurrentContext(); 288 if (context!=null) 289 { 290 Enumeration<String> names=context.getInitParameterNames(); 291 while (names!=null && names.hasMoreElements()) 292 { 293 String name =names.nextElement(); 294 if (name.startsWith("org.eclipse.jetty.security.") && 295 getInitParameter(name)==null) 296 setInitParameter(name,context.getInitParameter(name)); 297 } 298 299 //register a session listener to handle securing sessions when authentication is performed 300 context.getContextHandler().addEventListener(new HttpSessionListener() 301 { 302 303 public void sessionDestroyed(HttpSessionEvent se) 304 { 305 306 } 307 308 public void sessionCreated(HttpSessionEvent se) 309 { 310 //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 311 AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); 312 if (connection == null) 313 return; 314 Request request = connection.getRequest(); 315 if (request == null) 316 return; 317 318 if (request.isSecure()) 319 { 320 se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE); 321 } 322 } 323 }); 324 } 325 326 // complicated resolution of login and identity service to handle 327 // many different ways these can be constructed and injected. 328 329 if (_loginService==null) 330 { 331 _loginService=findLoginService(); 332 if (_loginService!=null) 333 _loginServiceShared=true; 334 } 335 336 if (_identityService==null) 337 { 338 339 if (_loginService!=null) 340 _identityService=_loginService.getIdentityService(); 341 342 if (_identityService==null) 343 _identityService=findIdentityService(); 344 345 if (_identityService==null && _realmName!=null) 346 _identityService=new DefaultIdentityService(); 347 } 348 349 if (_loginService!=null) 350 { 351 if (_loginService.getIdentityService()==null) 352 _loginService.setIdentityService(_identityService); 353 else if (_loginService.getIdentityService()!=_identityService) 354 throw new IllegalStateException("LoginService has different IdentityService to "+this); 355 } 356 357 if (!_loginServiceShared && _loginService instanceof LifeCycle) 358 ((LifeCycle)_loginService).start(); 359 360 if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null) 361 { 362 _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService); 363 if (_authenticator!=null) 364 _authMethod=_authenticator.getAuthMethod(); 365 } 366 367 if (_authenticator==null) 368 { 369 if (_realmName!=null) 370 { 371 LOG.warn("No ServerAuthentication for "+this); 372 throw new IllegalStateException("No ServerAuthentication"); 373 } 374 } 375 else 376 { 377 _authenticator.setConfiguration(this); 378 if (_authenticator instanceof LifeCycle) 379 ((LifeCycle)_authenticator).start(); 380 } 381 382 super.doStart(); 383 } 384 385 /* ------------------------------------------------------------ */ 386 /** 387 * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop() 388 */ 389 @Override doStop()390 protected void doStop() throws Exception 391 { 392 super.doStop(); 393 394 if (!_loginServiceShared && _loginService instanceof LifeCycle) 395 ((LifeCycle)_loginService).stop(); 396 397 } 398 399 /* ------------------------------------------------------------ */ checkSecurity(Request request)400 protected boolean checkSecurity(Request request) 401 { 402 switch(request.getDispatcherType()) 403 { 404 case REQUEST: 405 case ASYNC: 406 return true; 407 case FORWARD: 408 if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null) 409 { 410 request.removeAttribute("org.eclipse.jetty.server.welcome"); 411 return true; 412 } 413 return false; 414 default: 415 return false; 416 } 417 } 418 419 /* ------------------------------------------------------------ */ 420 /** 421 * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication() 422 */ isSessionRenewedOnAuthentication()423 public boolean isSessionRenewedOnAuthentication() 424 { 425 return _renewSession; 426 } 427 428 /* ------------------------------------------------------------ */ 429 /** Set renew the session on Authentication. 430 * <p> 431 * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session. 432 * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication() 433 */ setSessionRenewedOnAuthentication(boolean renew)434 public void setSessionRenewedOnAuthentication(boolean renew) 435 { 436 _renewSession=renew; 437 } 438 439 /* ------------------------------------------------------------ */ 440 /* 441 * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, 442 * javax.servlet.http.HttpServletRequest, 443 * javax.servlet.http.HttpServletResponse, int) 444 */ 445 @Override handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response)446 public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException 447 { 448 final Response base_response = baseRequest.getResponse(); 449 final Handler handler=getHandler(); 450 451 if (handler==null) 452 return; 453 454 final Authenticator authenticator = _authenticator; 455 456 if (checkSecurity(baseRequest)) 457 { 458 Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest); 459 460 // Check data constraints 461 if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo)) 462 { 463 if (!baseRequest.isHandled()) 464 { 465 response.sendError(Response.SC_FORBIDDEN); 466 baseRequest.setHandled(true); 467 } 468 return; 469 } 470 471 // is Auth mandatory? 472 boolean isAuthMandatory = 473 isAuthMandatory(baseRequest, base_response, constraintInfo); 474 475 if (isAuthMandatory && authenticator==null) 476 { 477 LOG.warn("No authenticator for: "+constraintInfo); 478 if (!baseRequest.isHandled()) 479 { 480 response.sendError(Response.SC_FORBIDDEN); 481 baseRequest.setHandled(true); 482 } 483 return; 484 } 485 486 // check authentication 487 Object previousIdentity = null; 488 try 489 { 490 Authentication authentication = baseRequest.getAuthentication(); 491 if (authentication==null || authentication==Authentication.NOT_CHECKED) 492 authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory); 493 494 if (authentication instanceof Authentication.Wrapped) 495 { 496 request=((Authentication.Wrapped)authentication).getHttpServletRequest(); 497 response=((Authentication.Wrapped)authentication).getHttpServletResponse(); 498 } 499 500 if (authentication instanceof Authentication.ResponseSent) 501 { 502 baseRequest.setHandled(true); 503 } 504 else if (authentication instanceof Authentication.User) 505 { 506 Authentication.User userAuth = (Authentication.User)authentication; 507 baseRequest.setAuthentication(authentication); 508 if (_identityService!=null) 509 previousIdentity = _identityService.associate(userAuth.getUserIdentity()); 510 511 if (isAuthMandatory) 512 { 513 boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity()); 514 if (!authorized) 515 { 516 response.sendError(Response.SC_FORBIDDEN, "!role"); 517 baseRequest.setHandled(true); 518 return; 519 } 520 } 521 522 handler.handle(pathInContext, baseRequest, request, response); 523 if (authenticator!=null) 524 authenticator.secureResponse(request, response, isAuthMandatory, userAuth); 525 } 526 else if (authentication instanceof Authentication.Deferred) 527 { 528 DeferredAuthentication deferred= (DeferredAuthentication)authentication; 529 baseRequest.setAuthentication(authentication); 530 531 try 532 { 533 handler.handle(pathInContext, baseRequest, request, response); 534 } 535 finally 536 { 537 previousIdentity = deferred.getPreviousAssociation(); 538 } 539 540 if (authenticator!=null) 541 { 542 Authentication auth=baseRequest.getAuthentication(); 543 if (auth instanceof Authentication.User) 544 { 545 Authentication.User userAuth = (Authentication.User)auth; 546 authenticator.secureResponse(request, response, isAuthMandatory, userAuth); 547 } 548 else 549 authenticator.secureResponse(request, response, isAuthMandatory, null); 550 } 551 } 552 else 553 { 554 baseRequest.setAuthentication(authentication); 555 if (_identityService!=null) 556 previousIdentity = _identityService.associate(null); 557 handler.handle(pathInContext, baseRequest, request, response); 558 if (authenticator!=null) 559 authenticator.secureResponse(request, response, isAuthMandatory, null); 560 } 561 } 562 catch (ServerAuthException e) 563 { 564 // jaspi 3.8.3 send HTTP 500 internal server error, with message 565 // from AuthException 566 response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage()); 567 } 568 finally 569 { 570 if (_identityService!=null) 571 _identityService.disassociate(previousIdentity); 572 } 573 } 574 else 575 handler.handle(pathInContext, baseRequest, request, response); 576 } 577 578 579 /* ------------------------------------------------------------ */ getCurrentSecurityHandler()580 public static SecurityHandler getCurrentSecurityHandler() 581 { 582 Context context = ContextHandler.getCurrentContext(); 583 if (context==null) 584 return null; 585 586 SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class); 587 return security; 588 } 589 590 /* ------------------------------------------------------------ */ logout(Authentication.User user)591 public void logout(Authentication.User user) 592 { 593 LOG.debug("logout {}",user); 594 LoginService login_service=getLoginService(); 595 if (login_service!=null) 596 { 597 login_service.logout(user.getUserIdentity()); 598 } 599 600 IdentityService identity_service=getIdentityService(); 601 if (identity_service!=null) 602 { 603 // TODO recover previous from threadlocal (or similar) 604 Object previous=null; 605 identity_service.disassociate(previous); 606 } 607 } 608 609 /* ------------------------------------------------------------ */ prepareConstraintInfo(String pathInContext, Request request)610 protected abstract Object prepareConstraintInfo(String pathInContext, Request request); 611 612 /* ------------------------------------------------------------ */ checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo)613 protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException; 614 615 /* ------------------------------------------------------------ */ isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)616 protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo); 617 618 /* ------------------------------------------------------------ */ checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)619 protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, 620 UserIdentity userIdentity) throws IOException; 621 622 623 /* ------------------------------------------------------------ */ 624 /* ------------------------------------------------------------ */ 625 public class NotChecked implements Principal 626 { getName()627 public String getName() 628 { 629 return null; 630 } 631 632 @Override toString()633 public String toString() 634 { 635 return "NOT CHECKED"; 636 } 637 getSecurityHandler()638 public SecurityHandler getSecurityHandler() 639 { 640 return SecurityHandler.this; 641 } 642 } 643 644 645 /* ------------------------------------------------------------ */ 646 /* ------------------------------------------------------------ */ 647 public static Principal __NO_USER = new Principal() 648 { 649 public String getName() 650 { 651 return null; 652 } 653 654 @Override 655 public String toString() 656 { 657 return "No User"; 658 } 659 }; 660 661 /* ------------------------------------------------------------ */ 662 /* ------------------------------------------------------------ */ 663 /** 664 * Nobody user. The Nobody UserPrincipal is used to indicate a partial state 665 * of authentication. A request with a Nobody UserPrincipal will be allowed 666 * past all authentication constraints - but will not be considered an 667 * authenticated request. It can be used by Authenticators such as 668 * FormAuthenticator to allow access to logon and error pages within an 669 * authenticated URI tree. 670 */ 671 public static Principal __NOBODY = new Principal() 672 { 673 public String getName() 674 { 675 return "Nobody"; 676 } 677 678 @Override 679 public String toString() 680 { 681 return getName(); 682 } 683 }; 684 685 } 686