1 //======================================================================== 2 //Copyright 2004-2008 Mort Bay Consulting Pty. Ltd. 3 //------------------------------------------------------------------------ 4 //Licensed under the Apache License, Version 2.0 (the "License"); 5 //you may not use this file except in compliance with the License. 6 //You may obtain a copy of the License at 7 //http://www.apache.org/licenses/LICENSE-2.0 8 //Unless required by applicable law or agreed to in writing, software 9 //distributed under the License is distributed on an "AS IS" BASIS, 10 //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 //See the License for the specific language governing permissions and 12 //limitations under the License. 13 //======================================================================== 14 15 //======================================================================== 16 //$Id: $ 17 //JBoss Jetty Integration 18 //------------------------------------------------------------------------ 19 //Licensed under LGPL. 20 //See license terms at http://www.gnu.org/licenses/lgpl.html 21 //======================================================================== 22 package org.jboss.jetty.security; 23 24 import java.io.Serializable; 25 import java.security.Principal; 26 import java.security.cert.X509Certificate; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.Set; 30 import java.util.Stack; 31 32 import javax.management.MBeanServer; 33 import javax.management.MBeanServerFactory; 34 import javax.management.ObjectName; 35 import javax.naming.Context; 36 import javax.naming.InitialContext; 37 import javax.naming.NamingException; 38 import javax.security.auth.Subject; 39 40 import org.jboss.jetty.JBossWebAppContext; 41 import org.jboss.logging.Logger; 42 import org.jboss.security.AuthenticationManager; 43 import org.jboss.security.NobodyPrincipal; 44 import org.jboss.security.RealmMapping; 45 import org.jboss.security.RunAsIdentity; 46 import org.jboss.security.SecurityAssociation; 47 import org.jboss.security.SimplePrincipal; 48 import org.jboss.security.SubjectSecurityManager; 49 import org.mortbay.jetty.security.HashSSORealm; 50 import org.mortbay.jetty.Request; 51 import org.mortbay.jetty.Response; 52 import org.mortbay.jetty.security.SSORealm; 53 import org.mortbay.jetty.security.UserRealm; 54 import org.mortbay.jetty.security.Credential; 55 56 /** 57 * JBossUserRealm 58 * An implementation of UserRealm that integrates with the JBossSX security 59 * manager associted with the web application. 60 * 61 * @author Scott_Stark@displayscape.com 62 * @author Cert Auth by pdawes@users.sf.net 63 * @author SSO Patch by steve.g@byu.edu 64 * @version $Revision: 1.9 $ 65 */ 66 67 public class JBossUserRealm implements UserRealm, SSORealm 68 { 69 private final Logger _log; 70 protected final String _realmName; 71 protected final String _subjAttrName; 72 protected SubjectSecurityManager _subjSecMgr = null; 73 protected AuthenticationManager _authMgr = null; 74 private final HashMap _users = new HashMap(); 75 protected RealmMapping _realmMapping = null; 76 protected JBossWebAppContext _jbossWebAppContext = null; 77 /* 78 * Since there is a seperate instance of JBossUserRealm per web-app 79 * regardless of whether the realm-name is the same, this creates an 80 * instance of HashSSORealm shared between all JBossUserRealms that have the 81 * same realm-name. 82 */ 83 private final static HashMap _sharedHashSSORealms = new HashMap(); 84 private String _ssoRealmName = null; 85 private HashSSORealm _ssoRealm = null; 86 87 88 89 /** 90 * JBossUserPrincipal 91 * 92 * 93 */ 94 static class JBossUserPrincipal implements Principal, Serializable 95 { 96 protected transient Logger _logRef; 97 protected transient JBossUserRealm _realm; 98 protected Principal _principal; 99 private String _password; 100 private Stack _roleStack= new Stack();; 101 JBossUserPrincipal()102 JBossUserPrincipal() {} 103 JBossUserPrincipal(String name, Logger log)104 JBossUserPrincipal(String name, Logger log) 105 { 106 _principal = new SimplePrincipal(name); 107 this._logRef = log; 108 109 if (log.isDebugEnabled()) 110 log.debug("created JBossUserRealm::JBossUserPrincipal: " + name); 111 } 112 associateWithRealm(JBossUserRealm realm)113 void associateWithRealm(JBossUserRealm realm) 114 { 115 this._realm = realm; 116 } 117 isAuthenticated(String password)118 private boolean isAuthenticated(String password) 119 { 120 boolean authenticated = false; 121 122 if (password == null) password = ""; 123 char[] passwordChars = password.toCharArray(); 124 125 if (_logRef.isDebugEnabled()) 126 _logRef.debug("authenticating: Name:" + _principal + " Password:****"/* +password */); 127 128 Subject subjectCopy = new Subject(); 129 130 if (_realm._subjSecMgr != null && _realm._subjSecMgr.isValid(this._principal, passwordChars, subjectCopy)) 131 { 132 if (_logRef.isDebugEnabled()) 133 _logRef.debug("authenticated: " + _principal); 134 135 SecurityAssociation.setPrincipal(_principal); 136 SecurityAssociation.setCredential(passwordChars); 137 SecurityAssociation.setSubject(subjectCopy); 138 authenticated = true; 139 } 140 else 141 { 142 _logRef.warn("authentication failure: " + _principal); 143 } 144 145 return authenticated; 146 } 147 equals(Object o)148 public boolean equals(Object o) 149 { 150 if (o == this) return true; 151 152 if (o == null) return false; 153 154 if (getClass() != o.getClass()) return false; 155 156 String myName = this.getName(); 157 String yourName = ((JBossUserPrincipal) o).getName(); 158 159 if (myName == null && yourName == null) return true; 160 161 if (myName != null && myName.equals(yourName)) return true; 162 163 return false; 164 } 165 166 getName()167 public String getName() 168 { 169 return _realm._realmMapping.getPrincipal(_principal).getName(); 170 } 171 172 authenticate(String password, Request request)173 public boolean authenticate(String password, Request request) 174 { 175 _password = password; 176 boolean authenticated = false; 177 authenticated = isAuthenticated(_password); 178 179 if (authenticated && _realm._subjSecMgr != null) 180 { 181 Subject subject = _realm._subjSecMgr.getActiveSubject(); 182 request.setAttribute(_realm._subjAttrName, subject); 183 } 184 185 return authenticated; 186 } 187 isAuthenticated()188 public boolean isAuthenticated() 189 { 190 return isAuthenticated(_password); 191 } 192 193 isUserInRole(String role)194 public boolean isUserInRole(String role) 195 { 196 boolean isUserInRole = false; 197 198 if (!_roleStack.isEmpty() && _roleStack.peek().equals(role)) 199 return true; 200 201 Set requiredRoles = Collections.singleton(new SimplePrincipal(role)); 202 if (_realm._realmMapping != null 203 && _realm._realmMapping.doesUserHaveRole(this._principal,requiredRoles)) 204 { 205 if (_logRef.isDebugEnabled()) 206 _logRef.debug("JBossUserPrincipal: " + _principal + " is in Role: " + role); 207 208 isUserInRole = true; 209 } 210 else 211 { 212 if (_logRef.isDebugEnabled()) 213 _logRef.debug("JBossUserPrincipal: " + _principal + " is NOT in Role: " + role); 214 } 215 216 return isUserInRole; 217 } 218 toString()219 public String toString() 220 { 221 return getName(); 222 } 223 push(String roleName)224 public void push (String roleName) 225 { 226 _roleStack.push(roleName); 227 } 228 pop()229 public void pop () 230 { 231 _roleStack.pop(); 232 } 233 } 234 235 /** 236 * JBossNobodyUserPrincipal 237 * Represents the default user. 238 */ 239 static class JBossNobodyUserPrincipal extends JBossUserPrincipal 240 { JBossNobodyUserPrincipal(Logger log)241 public JBossNobodyUserPrincipal(Logger log) 242 { 243 _principal = new NobodyPrincipal(); 244 this._logRef = log; 245 246 if (log.isDebugEnabled()) 247 log.debug("created JBossUserRealm::JBossNobodyUserPrincipal"); 248 } 249 isAuthenticated()250 public boolean isAuthenticated() 251 { 252 return true; 253 } 254 authenticate(String password, Request request)255 public boolean authenticate(String password, Request request) 256 { 257 return true; 258 } 259 260 } 261 262 /** 263 * JBossCertificatePrincipal 264 * Represents a user which has been authenticated elsewhere 265 * (e.g. at the fronting server), and thus doesnt have credentials 266 * 267 */ 268 static class JBossCertificatePrincipal extends JBossUserPrincipal 269 { 270 private X509Certificate[] _certs; 271 JBossCertificatePrincipal(String name, Logger log, X509Certificate[] certs)272 JBossCertificatePrincipal(String name, Logger log, X509Certificate[] certs) 273 { 274 super(name, log); 275 _certs = certs; 276 if (_logRef.isDebugEnabled()) 277 _logRef.debug("created JBossUserRealm::JBossCertificatePrincipal: "+ name); 278 } 279 isAuthenticated()280 public boolean isAuthenticated() 281 { 282 // TODO I'm dubious if this is correct??? 283 _logRef.debug("JBossUserRealm::isAuthenticated called"); 284 return true; 285 } 286 authenticate()287 public boolean authenticate() 288 { 289 boolean authenticated = false; 290 291 if (_logRef.isDebugEnabled()) 292 _logRef.debug("authenticating: Name:" + _principal); 293 294 // Authenticate using the cert as the credential 295 Subject subjectCopy = new Subject(); 296 if (_realm._subjSecMgr != null && _realm._subjSecMgr.isValid(_principal, _certs, subjectCopy)) 297 { 298 if (_logRef.isDebugEnabled()) 299 _logRef.debug("authenticated: " + _principal); 300 301 SecurityAssociation.setPrincipal(_principal); 302 SecurityAssociation.setCredential(_certs); 303 SecurityAssociation.setSubject(subjectCopy); 304 authenticated = true; 305 } 306 else 307 { 308 _logRef.warn("authentication failure: " + _principal); 309 } 310 311 return authenticated; 312 } 313 } 314 JBossUserRealm(String realmName, String subjAttrName)315 public JBossUserRealm(String realmName, String subjAttrName) 316 { 317 _realmName = realmName; 318 _log = Logger.getLogger(JBossUserRealm.class.getName() + "#"+ _realmName); 319 _subjAttrName = subjAttrName; 320 321 //always add a default user? 322 JBossUserPrincipal nobody = new JBossNobodyUserPrincipal(_log); 323 nobody.associateWithRealm(this); 324 _users.put("nobody", nobody); 325 } 326 init()327 public void init() 328 { 329 _log.debug("initialising realm "+_realmName); 330 try 331 { 332 InitialContext iniCtx = new InitialContext(); 333 Context securityCtx = (Context) iniCtx.lookup("java:comp/env/security"); 334 _authMgr = (AuthenticationManager) securityCtx.lookup("securityMgr"); 335 _realmMapping = (RealmMapping) securityCtx.lookup("realmMapping"); 336 iniCtx = null; 337 338 if (_authMgr instanceof SubjectSecurityManager) 339 _subjSecMgr = (SubjectSecurityManager) _authMgr; 340 } 341 catch (NamingException e) 342 { 343 _log.error("java:comp/env/security does not appear to be correctly set up", e); 344 } 345 _log.debug("...initialised"); 346 } 347 348 // this is going to cause contention - TODO ensureUser(String userName)349 private synchronized JBossUserPrincipal ensureUser(String userName) 350 { 351 JBossUserPrincipal user = (JBossUserPrincipal) _users.get(userName); 352 353 if (user == null) 354 { 355 user = new JBossUserPrincipal(userName, _log); 356 user.associateWithRealm(this); 357 _users.put(userName, user); 358 } 359 360 return user; 361 } 362 getPrincipal(String username)363 public Principal getPrincipal(String username) 364 { 365 return (Principal) _users.get(username); 366 } 367 368 /** 369 * @deprecated 370 */ getUserPrincipal(String username)371 public Principal getUserPrincipal(String username) 372 { 373 return (Principal) _users.get(username); 374 } 375 authenticate(String userName, Object credential, Request request)376 public Principal authenticate(String userName, Object credential, 377 Request request) 378 { 379 if (_log.isDebugEnabled()) 380 _log.debug("JBossUserPrincipal: " + userName); 381 382 // until we get DigestAuthentication sorted JBoss side... 383 JBossUserPrincipal user = null; 384 385 if (credential instanceof java.lang.String) // password 386 { 387 user = ensureUser(userName); 388 if (!user.authenticate((String) credential, request)) 389 { 390 user = null; 391 } 392 } 393 else if (credential instanceof X509Certificate[]) // certificate 394 { 395 X509Certificate[] certs = (X509Certificate[]) credential; 396 user = this.authenticateFromCertificates(certs); 397 } 398 399 if (user != null) 400 { 401 request.setAuthType(javax.servlet.http.HttpServletRequest.CLIENT_CERT_AUTH); 402 request.setUserPrincipal(user); 403 } 404 405 return user; 406 } 407 reauthenticate(Principal user)408 public boolean reauthenticate(Principal user) 409 { 410 return ((JBossUserPrincipal) user).isAuthenticated(); 411 } 412 413 /** 414 * @deprecated Use reauthenticate 415 */ isAuthenticated(Principal user)416 public boolean isAuthenticated(Principal user) 417 { 418 return ((JBossUserPrincipal) user).isAuthenticated(); 419 } 420 isUserInRole(Principal user, String role)421 public boolean isUserInRole(Principal user, String role) 422 { 423 return ((JBossUserPrincipal) user).isUserInRole(role); 424 } 425 authenticateFromCertificates( X509Certificate[] certs)426 public JBossUserPrincipal authenticateFromCertificates( 427 X509Certificate[] certs) 428 { 429 JBossCertificatePrincipal user = (JBossCertificatePrincipal) _users 430 .get(certs[0]); 431 432 if (user == null) 433 { 434 user = new JBossCertificatePrincipal(getFilterFromCertificate(certs[0]), _log, certs); 435 user.associateWithRealm(this); 436 _users.put(certs[0], user); 437 } 438 439 if (user.authenticate()) 440 { 441 _log.debug("authenticateFromCertificates - authenticated"); 442 return user; 443 } 444 445 _log.debug("authenticateFromCertificates - returning NULL"); 446 return null; 447 } 448 449 /** 450 * Takes an X509Certificate object and extracts the certificate's serial 451 * number and issuer in order to construct a unique string representing that 452 * certificate. 453 * 454 * @param cert the user's certificate. 455 * @return an LDAP filter for retrieving the user's entry. 456 */ getFilterFromCertificate(X509Certificate cert)457 private String getFilterFromCertificate(X509Certificate cert) 458 { 459 StringBuffer buff = new StringBuffer(); 460 String serialNumber = cert.getSerialNumber().toString(16).toUpperCase(); 461 462 if (serialNumber.length() % 2 != 0) buff.append("0"); 463 464 buff.append(serialNumber); 465 buff.append(" "); 466 buff.append(cert.getIssuerDN().toString()); 467 String filter = buff.toString(); 468 return filter; 469 } 470 disassociate(Principal user)471 public void disassociate(Principal user) 472 { 473 SecurityAssociation.clear(); 474 } 475 pushRole(Principal user, String role)476 public Principal pushRole(Principal user, String role) 477 { 478 RunAsIdentity runAs = new RunAsIdentity(role, (user==null?null:user.getName())); 479 if (user==null) 480 user = (JBossUserPrincipal)_users.get("nobody"); 481 482 //set up security for Jetty 483 ((JBossUserPrincipal)user).push(role); 484 //set up security for calls to jboss ejbs 485 SecurityAssociation.pushRunAsIdentity(runAs); 486 487 return user; 488 } 489 popRole(Principal user)490 public Principal popRole(Principal user) 491 { 492 ((JBossUserPrincipal)user).pop(); 493 //clear a run-as role set for jboss ejb calls 494 SecurityAssociation.popRunAsIdentity(); 495 return user; 496 } 497 logout(Principal user)498 public void logout(Principal user) 499 { 500 // yukky hack to try and force JBoss to actually 501 // flush the user from the jaas security manager's cache therefore 502 // forcing logincontext.logout() to be called 503 try 504 { 505 Principal pUser = user; 506 if (user instanceof JBossUserPrincipal) 507 pUser = ((JBossUserPrincipal) user)._principal; 508 509 java.util.ArrayList servers = MBeanServerFactory.findMBeanServer(null); 510 if (servers.size() != 1) 511 _log.warn("More than one MBeanServer found, choosing first"); 512 MBeanServer server = (MBeanServer) servers.get(0); 513 514 server.invoke(new ObjectName("jboss.security:service=JaasSecurityManager"), 515 "flushAuthenticationCache", 516 new Object[] { getName(), pUser }, 517 new String[] {"java.lang.String", "java.security.Principal" }); 518 } 519 catch (Exception e) 520 { 521 _log.error(e); 522 } 523 catch (Error err) 524 { 525 _log.error(err); 526 } 527 } 528 529 /** 530 * @param name The name of a Single Sign On realm. Realms that share a sso 531 * realm will share authentication for users. Null if no SSO 532 * realm. 533 */ setSSORealmName(String name)534 public void setSSORealmName(String name) 535 { 536 _ssoRealmName = name; 537 _ssoRealm = null; 538 } 539 540 /** 541 * @return The name of a Single Sign On realm. Realms that share a sso realm 542 * will share authentication for users. Null if no SSO realm. 543 */ getSSORealmName()544 public String getSSORealmName() 545 { 546 return _ssoRealmName; 547 } 548 getSingleSignOn(Request request, Response response)549 public Credential getSingleSignOn(Request request, Response response) 550 { 551 if (!isSSORealm()) return null; 552 Credential singleSignOnCredential = _ssoRealm.getSingleSignOn(request, 553 response); 554 if (_log.isDebugEnabled()) 555 _log.debug("getSingleSignOn principal=" 556 + request.getUserPrincipal() + " credential=" 557 + singleSignOnCredential); 558 return singleSignOnCredential; 559 560 } 561 setSingleSignOn(Request request, Response response, Principal principal, Credential credential)562 public void setSingleSignOn(Request request, Response response, 563 Principal principal, Credential credential) 564 { 565 if (!isSSORealm()) return; 566 if (_log.isDebugEnabled()) 567 _log.debug("setSingleSignOn called. principal=" + principal 568 + " credential=" + credential); 569 _ssoRealm.setSingleSignOn(request, response, principal, credential); 570 } 571 clearSingleSignOn(String username)572 public void clearSingleSignOn(String username) 573 { 574 if (!isSSORealm()) return; 575 576 if (_log.isDebugEnabled()) 577 _log.debug("clearSingleSignOn called. username=" + username); 578 _ssoRealm.clearSingleSignOn(username); 579 SecurityAssociation.setPrincipal(null); 580 SecurityAssociation.setCredential(null); 581 } 582 isSSORealm()583 private boolean isSSORealm() 584 { 585 if (_ssoRealm == null && _ssoRealmName != null) 586 { 587 synchronized (_sharedHashSSORealms) 588 { 589 _ssoRealm = (HashSSORealm) _sharedHashSSORealms 590 .get(_ssoRealmName); 591 if (_ssoRealm == null) 592 { 593 _log.debug("created SSORealm for " + _ssoRealmName); 594 _ssoRealm = new HashSSORealm(); 595 _sharedHashSSORealms.put(_ssoRealmName, _ssoRealm); 596 } 597 } 598 } 599 return _ssoRealm != null; 600 } 601 getName()602 public String getName() 603 { 604 return _realmName; 605 } 606 } 607