From b92e4a96aeacb7077f338c57ad7aed9d12a8f6bc Mon Sep 17 00:00:00 2001 From: remm Date: Tue, 5 Dec 2006 21:49:09 +0000 Subject: [PATCH] - Add experimental clustered SSO code. - Submitted by Fabien Carrion. git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@482797 13f79535-47bb-0310-9956-ffa450edef68 --- .../catalina/authenticator/SingleSignOn.java | 4 +- .../ha/authenticator/ClusterSingleSignOn.java | 448 +++++++++++++++++++++ .../authenticator/ClusterSingleSignOnListener.java | 180 +++++++++ .../ha/authenticator/SingleSignOnMessage.java | 187 +++++++++ .../ha/authenticator/mbeans-descriptors.xml | 25 ++ 5 files changed, 842 insertions(+), 2 deletions(-) create mode 100644 java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java create mode 100644 java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java create mode 100644 java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java create mode 100644 java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml diff --git a/java/org/apache/catalina/authenticator/SingleSignOn.java b/java/org/apache/catalina/authenticator/SingleSignOn.java index 23200c8f8..e399f4e2f 100644 --- a/java/org/apache/catalina/authenticator/SingleSignOn.java +++ b/java/org/apache/catalina/authenticator/SingleSignOn.java @@ -401,7 +401,7 @@ public class SingleSignOn if (entry != null) { if (containerLog.isDebugEnabled()) containerLog.debug(" Found cached principal '" + - entry.getPrincipal().getName() + "' with auth type '" + + (entry.getPrincipal() != null ? entry.getPrincipal().getName() : "") + "' with auth type '" + entry.getAuthType() + "'"); request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue()); // Only set security elements if reauthentication is not required @@ -600,7 +600,7 @@ public class SingleSignOn if (containerLog.isDebugEnabled()) containerLog.debug("Registering sso id '" + ssoId + "' for user '" + - principal.getName() + "' with auth type '" + authType + "'"); + (principal != null ? principal.getName() : "") + "' with auth type '" + authType + "'"); synchronized (cache) { cache.put(ssoId, new SingleSignOnEntry(principal, authType, diff --git a/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java b/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java new file mode 100644 index 000000000..c1332bc0f --- /dev/null +++ b/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java @@ -0,0 +1,448 @@ +/* + * Copyright 1999-2001,2004-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.catalina.ha.authenticator; + + +import java.security.Principal; + +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.Cluster; +import org.apache.catalina.Engine; +import org.apache.catalina.Host; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.Manager; +import org.apache.catalina.Session; +import org.apache.catalina.authenticator.SingleSignOn; +import org.apache.catalina.authenticator.SingleSignOnEntry; +import org.apache.catalina.ha.CatalinaCluster; +import org.apache.catalina.ha.ClusterManager; + + + +/** + * A Valve that supports a "single sign on" user experience on + * each nodes of a cluster, where the security identity of a user who successfully + * authenticates to one web application is propogated to other web applications and + * to other nodes cluster in the same security domain. For successful use, the following + * requirements must be met: + * + * + * @author Fabien Carrion + */ + +public class ClusterSingleSignOn + extends SingleSignOn { + + + // ----------------------------------------------------- Instance Variables + + + /** + * Descriptive information about this Valve implementation. + */ + protected static String info = + "org.apache.catalina.cluster.authenticator.ClusterSingleSignOn"; + + protected int messageNumber = 0; + + private ClusterSingleSignOnListener clusterSSOListener = null; + + + // ------------------------------------------------------------- Properties + + private CatalinaCluster cluster = null; + + + + /** + * Return descriptive information about this Valve implementation. + */ + public String getInfo() { + + return (info); + + } + + public CatalinaCluster getCluster() { + + return cluster; + + } + + public void setCluster(CatalinaCluster cluster) { + + this.cluster = cluster; + + } + + + // ------------------------------------------------------ Lifecycle Methods + + + /** + * Prepare for the beginning of active use of the public methods of this + * component. This method should be called after configure(), + * and before any of the public methods of the component are utilized. + * + * @exception LifecycleException if this component detects a fatal error + * that prevents this component from being used + */ + public void start() throws LifecycleException { + + super.start(); + + clusterSSOListener = new ClusterSingleSignOnListener(); + clusterSSOListener.setClusterSSO(this); + + // Load the cluster component, if any + try { + //the channel is already running + Cluster cluster = getCluster(); + // stop remove cluster binding + if(cluster == null) { + Container host = getContainer(); + if(host != null && host instanceof Host) { + cluster = host.getCluster(); + if(cluster != null && cluster instanceof CatalinaCluster) { + setCluster((CatalinaCluster) cluster); + getCluster().addClusterListener(clusterSSOListener); + } else { + Container engine = host.getParent(); + if(engine != null && engine instanceof Engine) { + cluster = engine.getCluster(); + if(cluster != null && cluster instanceof CatalinaCluster) { + setCluster((CatalinaCluster) cluster); + getCluster().addClusterListener(clusterSSOListener); + } + } else { + cluster = null; + } + } + } + } + if (cluster == null) { + throw new LifecycleException + ("There is no Cluster for ClusterSingleSignOn"); + } + + } catch (Throwable t) { + throw new LifecycleException + ("ClusterSingleSignOn exception during clusterLoad " + t); + } + + } + + + /** + * Gracefully terminate the active use of the public methods of this + * component. This method should be the last one called on a given + * instance of this component. + * + * @exception LifecycleException if this component detects a fatal error + * that needs to be reported + */ + public void stop() throws LifecycleException { + + super.stop(); + + if (getCluster() != null && getCluster() instanceof CatalinaCluster) { + getCluster().removeClusterListener(clusterSSOListener); + } + + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Return a String rendering of this object. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("ClusterSingleSignOn["); + if (container == null ) + sb.append("Container is null"); + else + sb.append(container.getName()); + sb.append("]"); + return (sb.toString()); + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Notify the cluster of the addition of a Session to + * an SSO session and associate the specified single + * sign on identifier with the specified Session on the + * local node. + * + * @param ssoId Single sign on identifier + * @param session Session to be associated + */ + protected void associate(String ssoId, Session session) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, session.getId()); + Manager mgr = session.getManager(); + if ((mgr != null) && (mgr instanceof ClusterManager)) + msg.setContextName(((ClusterManager) mgr).getName()); + + msg.setAction(SingleSignOnMessage.ADD_SESSION); + + cluster.sendClusterDomain(msg); + + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + associateLocal(ssoId, session); + + } + + protected void associateLocal(String ssoId, Session session) { + + super.associate(ssoId, session); + + } + + /** + * Notify the cluster of the removal of a Session from an + * SSO session and deregister the specified session. If it is the last + * session, then also get rid of the single sign on identifier on the + * local node. + * + * @param ssoId Single sign on identifier + * @param session Session to be deregistered + */ + protected void deregister(String ssoId, Session session) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, session.getId()); + Manager mgr = session.getManager(); + if ((mgr != null) && (mgr instanceof ClusterManager)) + msg.setContextName(((ClusterManager) mgr).getName()); + + msg.setAction(SingleSignOnMessage.DEREGISTER_SESSION); + + cluster.sendClusterDomain(msg); + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + deregisterLocal(ssoId, session); + + } + + protected void deregisterLocal(String ssoId, Session session) { + + super.deregister(ssoId, session); + + } + + /** + * Notifies the cluster that a single sign on session + * has been terminated due to a user logout, deregister + * the specified single sign on identifier, and invalidate + * any associated sessions on the local node. + * + * @param ssoId Single sign on identifier to deregister + */ + protected void deregister(String ssoId) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, null); + msg.setAction(SingleSignOnMessage.LOGOUT_SESSION); + + cluster.sendClusterDomain(msg); + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + deregisterLocal(ssoId); + + } + + protected void deregisterLocal(String ssoId) { + + super.deregister(ssoId); + + } + + /** + * Notifies the cluster of the creation of a new SSO entry + * and register the specified Principal as being associated + * with the specified value for the single sign on identifier. + * + * @param ssoId Single sign on identifier to register + * @param principal Associated user principal that is identified + * @param authType Authentication type used to authenticate this + * user principal + * @param username Username used to authenticate this user + * @param password Password used to authenticate this user + */ + protected void register(String ssoId, Principal principal, String authType, + String username, String password) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, null); + msg.setAction(SingleSignOnMessage.REGISTER_SESSION); + msg.setAuthType(authType); + msg.setUsername(username); + msg.setPassword(password); + + cluster.sendClusterDomain(msg); + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + registerLocal(ssoId, principal, authType, username, password); + + } + + protected void registerLocal(String ssoId, Principal principal, String authType, + String username, String password) { + + super.register(ssoId, principal, authType, username, password); + + } + + + /** + * Notifies the cluster of an update of the security credentials + * associated with an SSO session. Updates any SingleSignOnEntry + * found under key ssoId with the given authentication data. + *

+ * The purpose of this method is to allow an SSO entry that was + * established without a username/password combination (i.e. established + * following DIGEST or CLIENT-CERT authentication) to be updated with + * a username and password if one becomes available through a subsequent + * BASIC or FORM authentication. The SSO entry will then be usable for + * reauthentication. + *

+ * NOTE: Only updates the SSO entry if a call to + * SingleSignOnEntry.getCanReauthenticate() returns + * false; otherwise, it is assumed that the SSO entry already + * has sufficient information to allow reauthentication and that no update + * is needed. + * + * @param ssoId identifier of Single sign to be updated + * @param principal the Principal returned by the latest + * call to Realm.authenticate. + * @param authType the type of authenticator used (BASIC, CLIENT-CERT, + * DIGEST or FORM) + * @param username the username (if any) used for the authentication + * @param password the password (if any) used for the authentication + */ + protected void update(String ssoId, Principal principal, String authType, + String username, String password) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, null); + msg.setAction(SingleSignOnMessage.UPDATE_SESSION); + msg.setAuthType(authType); + msg.setUsername(username); + msg.setPassword(password); + + cluster.sendClusterDomain(msg); + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + updateLocal(ssoId, principal, authType, username, password); + + } + + protected void updateLocal(String ssoId, Principal principal, String authType, + String username, String password) { + + super.update(ssoId, principal, authType, username, password); + + } + + + /** + * Remove a single Session from a SingleSignOn and notify the cluster + * of the removal. Called when a session is timed out and no longer active. + * + * @param ssoId Single sign on identifier from which to remove the session. + * @param session the session to be removed. + */ + protected void removeSession(String ssoId, Session session) { + + if (cluster != null) { + messageNumber++; + SingleSignOnMessage msg = + new SingleSignOnMessage(cluster.getLocalMember(), + ssoId, session.getId()); + + Manager mgr = session.getManager(); + if ((mgr != null) && (mgr instanceof ClusterManager)) + msg.setContextName(((ClusterManager) mgr).getName()); + + msg.setAction(SingleSignOnMessage.REMOVE_SESSION); + + cluster.sendClusterDomain(msg); + if (containerLog.isDebugEnabled()) + containerLog.debug("SingleSignOnMessage Send with action " + + msg.getAction()); + } + + removeSessionLocal(ssoId, session); + } + + protected void removeSessionLocal(String ssoId, Session session) { + + super.removeSession(ssoId, session); + + } + +} diff --git a/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java b/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java new file mode 100644 index 000000000..9eddee6ce --- /dev/null +++ b/java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java @@ -0,0 +1,180 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.catalina.ha.authenticator; + +import java.util.Map; +import java.io.IOException; + +import org.apache.catalina.Session; +import org.apache.catalina.authenticator.SingleSignOnEntry; +import org.apache.catalina.ha.ClusterManager; +import org.apache.catalina.ha.ClusterMessage; +import org.apache.catalina.ha.ClusterListener; + +/** + * Receive replicated SingleSignOnMessage form other cluster node. + * + * @author Fabien Carrion + */ +public class ClusterSingleSignOnListener extends ClusterListener { + + /** + * The descriptive information about this implementation. + */ + protected static final String info = "org.apache.catalina.session.ClusterSingleSignOnListener/1.0"; + + // ------------------------------------------------------------- Properties + + private ClusterSingleSignOn clusterSSO = null; + + + //--Constructor--------------------------------------------- + + public ClusterSingleSignOnListener() { + } + + //--Logic--------------------------------------------------- + + /** + * Return descriptive information about this implementation. + */ + public String getInfo() { + + return (info); + + } + + public ClusterSingleSignOn getClusterSSO() { + + return clusterSSO; + + } + + public void setClusterSSO(ClusterSingleSignOn clusterSSO) { + + this.clusterSSO = clusterSSO; + + } + + + /** + * Callback from the cluster, when a message is received, The cluster will + * broadcast it invoking the messageReceived on the receiver. + * + * @param myobj + * ClusterMessage - the message received from the cluster + */ + public void messageReceived(ClusterMessage myobj) { + if (myobj != null && myobj instanceof SingleSignOnMessage) { + SingleSignOnMessage msg = (SingleSignOnMessage) myobj; + int action = msg.getAction(); + Session session = null; + + if (log.isDebugEnabled()) + log.debug("SingleSignOnMessage Received with action " + + msg.getAction()); + + switch(action) { + case SingleSignOnMessage.ADD_SESSION: + session = getSession(msg.getSessionId(), + msg.getContextName()); + if (session != null) + clusterSSO.associateLocal(msg.getSsoId(), session); + break; + case SingleSignOnMessage.DEREGISTER_SESSION: + session = getSession(msg.getSessionId(), + msg.getContextName()); + if (session != null) + clusterSSO.deregisterLocal(msg.getSsoId(), session); + break; + case SingleSignOnMessage.LOGOUT_SESSION: + clusterSSO.deregisterLocal(msg.getSsoId()); + break; + case SingleSignOnMessage.REGISTER_SESSION: + clusterSSO.registerLocal(msg.getSsoId(), null, msg.getAuthType(), + msg.getUsername(), msg.getPassword()); + break; + case SingleSignOnMessage.UPDATE_SESSION: + clusterSSO.updateLocal(msg.getSsoId(), null, msg.getAuthType(), + msg.getUsername(), msg.getPassword()); + break; + case SingleSignOnMessage.REMOVE_SESSION: + session = getSession(msg.getSessionId(), + msg.getContextName()); + if (session != null) + clusterSSO.removeSessionLocal(msg.getSsoId(), session); + break; + } + } + } + + /** + * Accept only SingleSignOnMessage + * + * @param msg + * ClusterMessage + * @return boolean - returns true to indicate that messageReceived should be + * invoked. If false is returned, the messageReceived method will + * not be invoked. + */ + public boolean accept(ClusterMessage msg) { + return (msg instanceof SingleSignOnMessage); + } + + + private Session getSession(String sessionId, String ctxname) { + + Map managers = clusterSSO.getCluster().getManagers() ; + Session session = null; + + if (ctxname == null) { + java.util.Iterator i = managers.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + ClusterManager mgr = (ClusterManager) managers.get(key); + if (mgr != null) { + try { + session = mgr.findSession(sessionId); + } catch (IOException io) { + log.error("Session doesn't exist:" + io); + } + return session; + } else { + //this happens a lot before the system has started + // up + if (log.isDebugEnabled()) + log.debug("Context manager doesn't exist:" + + key); + } + } + } else { + ClusterManager mgr = (ClusterManager) managers.get(ctxname); + if (mgr != null) { + try { + session = mgr.findSession(sessionId); + } catch (IOException io) { + log.error("Session doesn't exist:" + io); + } + return session; + } else if (log.isErrorEnabled()) + log.error("Context manager doesn't exist:" + ctxname); + } + + return null; + } +} + diff --git a/java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java b/java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java new file mode 100644 index 000000000..e2cd97cfb --- /dev/null +++ b/java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java @@ -0,0 +1,187 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.catalina.ha.authenticator; + +import java.io.Serializable; + +import org.apache.catalina.ha.ClusterMessage; +import org.apache.catalina.tribes.Member; + +/** + * Contains the SingleSignOn data, read and written by the ClusterSingleSignOn + * @author Fabien Carrion + */ + +public class SingleSignOnMessage implements ClusterMessage, Serializable { + + public static final int ADD_SESSION = 1; + public static final int DEREGISTER_SESSION = 2; + public static final int LOGOUT_SESSION = 3; + public static final int REGISTER_SESSION = 4; + public static final int UPDATE_SESSION = 5; + public static final int REMOVE_SESSION = 6; + + private int action = -1; + private String ssoId = null; + private String ctxname = null; + private String sessionId = null; + private String authType = null; + private String password = null; + private String username = null; + + private Member address = null; + private long timestamp = 0; + private String uniqueId = null; + + public SingleSignOnMessage(Member source, + String ssoId, + String sessionId) { + this.address = source; + this.ssoId = ssoId; + this.sessionId = sessionId; + } + + /** + * Get the address that this message originated from. This would be set + * if the message was being relayed from a host other than the one + * that originally sent it. + */ + public Member getAddress() { + return address; + } + + /** + * Called by the cluster before sending it to the other + * nodes. + * + * @param member Member + */ + public void setAddress(Member member) { + this.address = address; + } + + /** + * Timestamp message. + * + * @return long + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Called by the cluster before sending out + * the message. + * + * @param timestamp The timestamp + */ + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + /** + * Each message must have a unique ID, in case of using async replication, + * and a smart queue, this id is used to replace messages not yet sent. + * + * @return String + */ + public String getUniqueId() { + if (this.uniqueId != null) + return this.uniqueId; + StringBuffer result = new StringBuffer(getSsoId()); + result.append("#-#"); + result.append(System.currentTimeMillis()); + return result.toString(); + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public int getAction() { + return action; + } + + public void setAction(int action) { + this.action = action; + } + + public String getSsoId() { + return ssoId; + } + + public void setSsoId(String ssoId) { + this.ssoId = ssoId; + } + + public String getContextName() { + return ctxname; + } + + public void setContextName(String ctxname) { + this.ctxname = ctxname; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Return a String rendering of this object. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("SingleSignOnMessage[action="); + sb.append(getAction()).append(", ssoId=").append(getSsoId()); + sb.append(", sessionId=").append(getSessionId()).append(", username="); + sb.append(getUsername()).append("]"); + return (sb.toString()); + + } + +} diff --git a/java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml b/java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml new file mode 100644 index 000000000..7920f9880 --- /dev/null +++ b/java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + -- 2.11.0