1 22 package org.jboss.web.tomcat.tc6.sso; 23 24 import java.io.Serializable ; 25 import java.security.Principal ; 26 import java.util.HashSet ; 27 import java.util.Set ; 28 29 import javax.management.MBeanServer ; 30 import javax.management.ObjectName ; 31 import javax.transaction.Status ; 32 import javax.transaction.TransactionManager ; 33 34 import org.apache.catalina.LifecycleException; 35 import org.apache.catalina.LifecycleListener; 36 import org.apache.catalina.Session; 37 import org.apache.catalina.util.LifecycleSupport; 38 import org.jboss.cache.AbstractCacheListener; 39 import org.jboss.cache.Cache; 40 import org.jboss.cache.Fqn; 41 import org.jboss.cache.InvocationContext; 42 import org.jboss.cache.Region; 43 import org.jboss.cache.RegionNotEmptyException; 44 import org.jboss.cache.config.Option; 45 import org.jboss.cache.jmx.CacheJmxWrapperMBean; 46 import org.jboss.logging.Logger; 47 import org.jboss.mx.util.MBeanProxyExt; 48 import org.jboss.mx.util.MBeanServerLocator; 49 import org.jboss.util.NestedRuntimeException; 50 51 58 public final class TreeCacheSSOClusterManager 59 extends AbstractCacheListener 60 implements SSOClusterManager 61 { 62 64 68 private static final String CREDENTIALS = "credentials"; 69 70 74 private static final String SSO = "SSO"; 75 76 80 private static final String SESSIONS = "sessions"; 81 82 85 private static final String KEY = "key"; 86 87 90 public static final String DEFAULT_GLOBAL_CACHE_NAME = 91 "jboss.cache:service=TomcatClusteringCache"; 92 93 private static final Option GRAVITATE_OPTION = new Option(); 94 95 static 96 { 97 GRAVITATE_OPTION.setForceDataGravitation(true); 98 } 99 100 102 105 private ThreadLocal beingLocallyAdded = new ThreadLocal (); 106 107 110 private ThreadLocal beingLocallyRemoved = new ThreadLocal (); 111 112 115 private ThreadLocal beingRemotelyRemoved = new ThreadLocal (); 116 117 120 private ObjectName cacheObjectName = null; 121 122 125 private String cacheName = null; 126 127 130 private Cache cache = null; 131 132 135 private TransactionManager tm = null; 136 137 140 private LifecycleSupport lifecycle = new LifecycleSupport(this); 141 142 145 private Logger log = Logger.getLogger(getClass().getName());; 146 147 150 private boolean registeredAsListener = false; 151 152 155 private MBeanServer server = null; 156 157 160 private ClusteredSingleSignOn ssoValve = null; 161 162 165 private boolean started = false; 166 167 170 private boolean treeCacheAvailable = false; 171 172 175 private boolean missingCacheErrorLogged = false; 176 177 180 private Serializable localAddress = null; 181 182 184 185 188 public TreeCacheSSOClusterManager() 189 { 190 server = MBeanServerLocator.locateJBoss(); 192 if (server == null) 193 server = MBeanServerLocator.locate(); 194 } 195 196 197 201 public TreeCacheSSOClusterManager(MBeanServer server) 202 { 203 this.server = server; 204 } 205 206 207 209 public String getCacheName() 210 { 211 return cacheName; 212 } 213 214 public void setCacheName(String objectName) 215 throws Exception 216 { 217 if (objectName == null) 218 { 219 setCacheObjectName(null); 220 } 221 else if (objectName.equals(cacheName) == false) 222 { 223 setCacheObjectName(new ObjectName (objectName)); 224 } 225 } 226 227 public ObjectName getCacheObjectName() 228 { 229 return cacheObjectName; 230 } 231 232 public void setCacheObjectName(ObjectName objectName) 233 throws Exception 234 { 235 if ((objectName != null && objectName.equals(cacheObjectName)) 237 || (cacheObjectName != null && cacheObjectName.equals(objectName)) 238 || (objectName == null && cacheObjectName == null)) 239 { 240 return; 241 } 242 243 removeAsCacheListener(); 244 this.tm = null; 245 246 this.cacheObjectName = objectName; 247 this.cacheName = (objectName == null 248 ? null 249 : objectName.getCanonicalName()); 250 251 if (false == isTreeCacheAvailable(true)) 252 { 253 if (started) 254 { 255 logMissingCacheError(); 256 } 257 else 258 { 259 log.info("Cannot find TreeCache using " + cacheName + " -- tree" + 261 "CacheName must be set to point to a running TreeCache " + 262 "before ClusteredSingleSignOn can handle requests"); 263 } 264 } 265 } 266 267 269 275 public void addSession(String ssoId, Session session) 276 { 277 if (ssoId == null || session == null) 278 { 279 return; 280 } 281 282 if (!checkTreeCacheAvailable()) 283 { 284 return; 285 } 286 287 if (log.isTraceEnabled()) 288 { 289 log.trace("addSession(): adding Session " + session.getId() + 290 " to cached session set for SSO " + ssoId); 291 } 292 293 Fqn fqn = getSessionsFqn(ssoId); 294 boolean doTx = false; 295 try 296 { 297 if (tm == null) 300 configureFromCache(); 301 302 if(tm.getTransaction() == null) 305 doTx = true; 306 307 if(doTx) 308 tm.begin(); 309 310 Set sessions = getSessionSet(fqn, true); 311 sessions.add(new SessionAddress(session.getId(), localAddress)); 312 putInTreeCache(fqn, sessions); 313 } 314 catch (Exception e) 315 { 316 try 317 { 318 if(doTx) 319 tm.setRollbackOnly(); 320 } 321 catch (Exception ignored) 322 { 323 } 324 String sessId = (session == null ? "NULL" : session.getId()); 325 log.error("caught exception adding session " + sessId + 326 " to SSO id " + ssoId, e); 327 } 328 finally 329 { 330 if (doTx) 331 endTransaction(); 332 } 333 } 334 335 336 342 public ClusteredSingleSignOn getSingleSignOnValve() 343 { 344 return ssoValve; 345 } 346 347 348 356 public void setSingleSignOnValve(ClusteredSingleSignOn valve) 357 { 358 ssoValve = valve; 359 } 360 361 362 368 public void logout(String ssoId) 369 { 370 if (!checkTreeCacheAvailable()) 371 { 372 return; 373 } 374 375 if (ssoId.equals(beingLocallyRemoved.get())) 377 { 378 return; 379 } 380 381 beingLocallyRemoved.set(ssoId); 384 385 if (log.isTraceEnabled()) 386 { 387 log.trace("Registering logout of SSO " + ssoId + 388 " in clustered cache"); 389 } 390 391 Fqn fqn = getSingleSignOnFqn(ssoId); 392 393 try 394 { 395 removeFromTreeCache(fqn); 396 } 397 catch (Exception e) 398 { 399 log.error("Exception attempting to remove node " + 400 fqn.toString() + " from TreeCache", e); 401 } 402 finally 403 { 404 beingLocallyRemoved.set(null); 405 } 406 } 407 408 409 418 public SingleSignOnEntry lookup(String ssoId) 419 { 420 if (!checkTreeCacheAvailable()) 421 { 422 return null; 423 } 424 425 SingleSignOnEntry entry = null; 426 Fqn fqn = getCredentialsFqn(ssoId); 428 try 429 { 430 SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn); 431 if (data != null) 432 { 433 entry = new SingleSignOnEntry(null, 434 data.getAuthType(), 435 data.getUsername(), 436 data.getPassword()); 437 } 438 } 439 catch (Exception e) 440 { 441 log.error("caught exception looking up SSOCredentials for SSO id " + 442 ssoId, e); 443 } 444 return entry; 445 } 446 447 448 457 public void register(String ssoId, String authType, 458 String username, String password) 459 { 460 if (!checkTreeCacheAvailable()) 461 { 462 return; 463 } 464 465 if (log.isTraceEnabled()) 466 { 467 log.trace("Registering SSO " + ssoId + " in clustered cache"); 468 } 469 470 storeSSOData(ssoId, authType, username, password); 471 } 472 473 474 480 public void removeSession(String ssoId, Session session) 481 { 482 if (ssoId == null || session == null) 483 { 484 return; 485 } 486 487 if (!checkTreeCacheAvailable()) 488 { 489 return; 490 } 491 492 if (ssoId.equals(beingRemotelyRemoved.get())) 495 { 496 return; 497 } 498 499 if (log.isTraceEnabled()) 500 { 501 log.trace("removeSession(): removing Session " + session.getId() + 502 " from cached session set for SSO " + ssoId); 503 } 504 505 Fqn fqn = getSessionsFqn(ssoId); 506 boolean doTx = false; 507 boolean removing = false; 508 try 509 { 510 if (tm == null) 513 configureFromCache(); 514 515 if(tm.getTransaction() == null) 518 doTx = true; 519 520 if(doTx) 521 tm.begin(); 522 523 Set sessions = getSessionSet(fqn, false); 524 if (sessions != null) 525 { 526 sessions.remove(new SessionAddress(session.getId(), localAddress)); 527 if (sessions.size() == 0) 528 { 529 531 removing = true; 534 beingLocallyRemoved.set(ssoId); 535 removeFromTreeCache(getSingleSignOnFqn(ssoId)); 536 } 537 else 538 { 539 putInTreeCache(fqn, sessions); 540 } 541 } 542 } 543 catch (Exception e) 544 { 545 try 546 { 547 if(doTx) 548 tm.setRollbackOnly(); 549 } 550 catch (Exception x) 551 { 552 } 553 554 String sessId = (session == null ? "NULL" : session.getId()); 555 log.error("caught exception removing session " + sessId + 556 " from SSO id " + ssoId, e); 557 } 558 finally 559 { 560 try 561 { 562 if (removing) 563 { 564 beingLocallyRemoved.set(null); 565 } 566 } 567 finally 568 { 569 if (doTx) 570 endTransaction(); 571 } 572 } 573 } 574 575 576 586 public void updateCredentials(String ssoId, String authType, 587 String username, String password) 588 { 589 if (!checkTreeCacheAvailable()) 590 { 591 return; 592 } 593 594 if (log.isTraceEnabled()) 595 { 596 log.trace("Updating credentials for SSO " + ssoId + 597 " in clustered cache"); 598 } 599 600 storeSSOData(ssoId, authType, username, password); 601 } 602 603 604 606 615 public void nodeRemoved(Fqn fqn) 616 { 617 String ssoId = getIdFromFqn(fqn); 618 619 if (ssoId == null) 620 return; 621 622 if (ssoId.equals(beingLocallyRemoved.get())) 624 { 625 return; 626 } 627 628 beingRemotelyRemoved.set(ssoId); 629 630 try 631 { 632 if (log.isTraceEnabled()) 633 { 634 log.trace("received a node removed message for SSO " + ssoId); 635 } 636 637 ssoValve.deregister(ssoId); 638 } 639 finally 640 { 641 beingRemotelyRemoved.set(null); 642 } 643 644 } 645 646 661 public void nodeModified(Fqn fqn) 662 { 663 if (CREDENTIALS.equals(getTypeFromFqn(fqn)) == false) 665 { 666 return; 667 } 668 669 String ssoId = getIdFromFqn(fqn); 671 if (ssoId.equals(beingLocallyAdded.get())) 673 { 674 return; 675 } 676 677 SingleSignOnEntry sso = ssoValve.localLookup(ssoId); 678 if (sso == null || sso.getCanReauthenticate()) 679 { 680 return; 682 } 683 684 if (log.isTraceEnabled()) 685 { 686 log.trace("received a credentials modified message for SSO " + ssoId); 687 } 688 689 try 692 { 693 SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn); 694 if (data != null) 695 { 696 String authType = data.getAuthType(); 699 String username = data.getUsername(); 700 String password = data.getPassword(); 701 702 if (log.isTraceEnabled()) 703 { 704 log.trace("CredentialUpdater: Updating credentials for SSO " + sso); 705 } 706 707 synchronized (sso) 708 { 709 Principal p = sso.getPrincipal(); 711 sso.updateCredentials(p, authType, username, password); 712 } 713 } 714 } 715 catch (Exception e) 716 { 717 log.error("failed to update credentials for SSO " + ssoId, e); 718 } 719 } 720 721 722 724 725 730 public void addLifecycleListener(LifecycleListener listener) 731 { 732 lifecycle.addLifecycleListener(listener); 733 } 734 735 736 740 public LifecycleListener[] findLifecycleListeners() 741 { 742 return lifecycle.findLifecycleListeners(); 743 } 744 745 746 751 public void removeLifecycleListener(LifecycleListener listener) 752 { 753 lifecycle.removeLifecycleListener(listener); 754 } 755 756 765 public void start() throws LifecycleException 766 { 767 if (started) 769 { 770 throw new LifecycleException 771 ("TreeCacheSSOClusterManager already Started"); 772 } 773 774 try 775 { 776 if (isTreeCacheAvailable(true)) 777 { 778 integrateWithCache(); 779 } 780 } 781 catch (Exception e) 782 { 783 throw new LifecycleException("Caught exception looking up " + 784 "TransactionManager from TreeCache", e); 785 } 786 787 started = true; 788 789 lifecycle.fireLifecycleEvent(START_EVENT, null); 791 } 792 793 794 803 public void stop() throws LifecycleException 804 { 805 if (!started) 807 { 808 throw new LifecycleException 809 ("TreeCacheSSOClusterManager not Started"); 810 } 811 812 started = false; 813 814 lifecycle.fireLifecycleEvent(STOP_EVENT, null); 816 } 817 818 819 821 private Object getFromTreeCache(Fqn fqn) throws Exception 822 { 823 InvocationContext ctx = cache.getInvocationContext(); 824 Option existing = ctx.getOptionOverrides(); 825 try 826 { 827 ctx.setOptionOverrides(GRAVITATE_OPTION); 828 return cache.get(fqn, KEY); 829 } 830 finally 831 { 832 ctx.setOptionOverrides(existing); 833 } 834 } 835 836 private Fqn getCredentialsFqn(String ssoid) 837 { 838 Object [] objs = new Object []{SSO, ssoid, CREDENTIALS}; 839 return new Fqn(objs); 840 } 841 842 private Fqn getSessionsFqn(String ssoid) 843 { 844 Object [] objs = new Object []{SSO, ssoid, SESSIONS}; 845 return new Fqn(objs); 846 } 847 848 private Fqn getSingleSignOnFqn(String ssoid) 849 { 850 Object [] objs = new Object []{SSO, ssoid}; 851 return new Fqn(objs); 852 } 853 854 860 private String getIdFromFqn(Fqn fqn) 861 { 862 String id = null; 863 if (fqn.size() > 1 && SSO.equals(fqn.get(0))) 864 { 865 id = (String ) fqn.get(1); 866 } 867 return id; 868 } 869 870 private Set getSessionSet(Fqn fqn, boolean create) 871 throws Exception 872 { 873 Set sessions = (Set ) getFromTreeCache(fqn); 874 if (create && sessions == null) 875 { 876 sessions = new HashSet (); 877 } 878 return sessions; 879 } 880 881 890 private String getTypeFromFqn(Fqn fqn) 891 { 892 String type = null; 893 if (fqn.size() > 2 && SSO.equals(fqn.get(0))) 894 type = (String ) fqn.get(2); 895 return type; 896 } 897 898 906 private void configureFromCache() throws Exception 907 { 908 tm = cache.getTransactionManager(); 909 910 if (tm == null) 911 { 912 throw new IllegalStateException ("Cache does not have a " + 913 "transaction manager; please " + 914 "configure a valid " + 915 "TransactionManagerLookupClass"); 916 } 917 918 Object address = cache.getLocalAddress(); 920 if (address instanceof Serializable ) 923 localAddress = (Serializable ) address; 924 else 925 localAddress = address.toString(); 926 } 927 928 private void endTransaction() 929 { 930 try 931 { 932 if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK) 933 { 934 tm.commit(); 935 } 936 else 937 { 938 tm.rollback(); 939 } 940 } 941 catch (Exception e) 942 { 943 log.error(e); 944 throw new NestedRuntimeException("TreeCacheSSOClusterManager.endTransaction(): ", e); 945 } 946 } 947 948 957 private synchronized boolean isTreeCacheAvailable(boolean forceCheck) 958 { 959 if (forceCheck || treeCacheAvailable == false) 960 { 961 boolean available = (cacheObjectName != null); 962 if (available) 963 { 964 Set s = server.queryMBeans(cacheObjectName, null); 965 available = s.size() > 0; 966 if (available) 967 { 968 try 969 { 970 if (started) 976 integrateWithCache(); 977 setMissingCacheErrorLogged(false); 978 } 979 catch (Exception e) 980 { 981 log.error("Caught exception configuring from cache " + 982 cacheObjectName, e); 983 available = false; 984 } 985 } 986 } 987 treeCacheAvailable = available; 988 } 989 return treeCacheAvailable; 990 } 991 992 private boolean checkTreeCacheAvailable() 993 { 994 boolean avail = isTreeCacheAvailable(false); 995 if (!avail) 996 logMissingCacheError(); 997 return avail; 998 } 999 1000 private void putInTreeCache(Fqn fqn, Object data) throws Exception 1001 { 1002 InvocationContext ctx = cache.getInvocationContext(); 1003 Option existing = ctx.getOptionOverrides(); 1004 try 1005 { 1006 ctx.setOptionOverrides(GRAVITATE_OPTION); 1007 cache.put(fqn, KEY, data); 1008 } 1009 finally 1010 { 1011 ctx.setOptionOverrides(existing); 1012 } 1013 } 1014 1015 private void integrateWithCache() throws Exception 1016 { 1017 if (cache == null) 1018 { 1019 CacheJmxWrapperMBean mbean = (CacheJmxWrapperMBean) MBeanProxyExt.create(CacheJmxWrapperMBean.class, 1021 getCacheObjectName()); 1022 cache = mbean.getCache(); 1023 1024 configureFromCache(); 1026 1027 activateCacheRegion(); 1029 1030 registerAsCacheListener(); 1031 1032 log.debug("Successfully integrated with cache service " + cacheObjectName); 1033 } 1034 } 1035 1036 1037 1043 private void activateCacheRegion() throws Exception 1044 { 1045 if (cache.getConfiguration().isInactiveOnStartup()) 1046 { 1047 if (cache.getConfiguration().isUseRegionBasedMarshalling()) 1048 { 1049 Region region =cache.getRegion(Fqn.fromString("/" + SSO), true); 1050 try 1051 { 1052 region.activate(); 1053 } 1054 catch (RegionNotEmptyException e) 1055 { 1056 log.debug(SSO + " region already active", e); 1057 } 1058 } 1059 } 1060 } 1061 1062 1068 private void registerAsCacheListener() throws Exception 1069 { 1070 cache.addCacheListener(this); 1071 registeredAsListener = true; 1072 } 1073 1074 1075 1081 private void removeAsCacheListener() throws Exception 1082 { 1083 if (registeredAsListener && cache != null) 1084 { 1085 cache.removeCacheListener(this); 1086 registeredAsListener = false; 1087 } 1088 } 1089 1090 private void removeFromTreeCache(Fqn fqn) throws Exception 1091 { 1092 InvocationContext ctx = cache.getInvocationContext(); 1093 Option existing = ctx.getOptionOverrides(); 1094 try 1095 { 1096 ctx.setOptionOverrides(GRAVITATE_OPTION); 1097 cache.remove(fqn); 1098 } 1099 finally 1100 { 1101 ctx.setOptionOverrides(existing); 1102 } 1103 } 1104 1105 1121 private void storeSSOData(String ssoId, String authType, String username, 1122 String password) 1123 { 1124 SSOCredentials data = new SSOCredentials(authType, username, password); 1125 1126 beingLocallyAdded.set(ssoId); 1129 1130 try 1131 { 1132 putInTreeCache(getCredentialsFqn(ssoId), data); 1133 } 1134 catch (Exception e) 1135 { 1136 log.error("Exception attempting to add TreeCache nodes for SSO " + 1137 ssoId, e); 1138 } 1139 finally 1140 { 1141 beingLocallyAdded.set(null); 1142 } 1143 } 1144 1145 private boolean isMissingCacheErrorLogged() 1146 { 1147 return missingCacheErrorLogged; 1148 } 1149 1150 private void setMissingCacheErrorLogged(boolean missingCacheErrorLogged) 1151 { 1152 this.missingCacheErrorLogged = missingCacheErrorLogged; 1153 } 1154 1155 private void logMissingCacheError() 1156 { 1157 StringBuffer msg = new StringBuffer ("Cannot find TreeCache using "); 1158 msg.append(getCacheName()); 1159 msg.append(" -- TreeCache must be started before ClusteredSingleSignOn "); 1160 msg.append("can handle requests"); 1161 1162 if (isMissingCacheErrorLogged()) 1163 { 1164 log.warn(msg); 1166 } 1167 else 1168 { 1169 log.error(msg); 1170 setMissingCacheErrorLogged(true); 1172 } 1173 } 1174 1175 1177 1182 public static class SSOCredentials 1183 implements Serializable 1184 { 1185 1186 private static final long serialVersionUID = 5704877226920571663L; 1187 1188 private String authType = null; 1189 private String password = null; 1190 private String username = null; 1191 1192 1200 private SSOCredentials(String authType, String username, String password) 1201 { 1202 this.authType = authType; 1203 this.username = username; 1204 this.password = password; 1205 } 1206 1207 1212 public String getUsername() 1213 { 1214 return username; 1215 } 1216 1217 1222 public String getAuthType() 1223 { 1224 return authType; 1225 } 1226 1227 1233 private String getPassword() 1234 { 1235 return password; 1236 } 1237 1238 } 1240 static class SessionAddress implements Serializable 1241 { 1242 1243 private static final long serialVersionUID = -3702932999380140004L; 1244 1245 Serializable address; 1246 String sessionId; 1247 1248 SessionAddress(String sessionId, Serializable address) 1249 { 1250 this.sessionId = sessionId; 1251 this.address = address; 1252 } 1253 1254 public boolean equals(Object obj) 1255 { 1256 if (this == obj) 1257 return true; 1258 1259 if (!(obj instanceof SessionAddress)) 1260 return false; 1261 1262 SessionAddress other = (SessionAddress) obj; 1263 1264 return (sessionId.equals(other.sessionId) 1265 && address.equals(other.address)); 1266 } 1267 1268 public int hashCode() 1269 { 1270 int total = (19 * 43) + sessionId.hashCode(); 1271 return ((total * 43) + address.hashCode()); 1272 } 1273 1274 1275 } 1276 1277} 1279 | Popular Tags |