You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2009/07/25 01:19:11 UTC

svn commit: r797689 - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/session/ core/src/main/java/org/apache/shiro/session/mgt/ web/src/main/java/org/apache/shiro/web/session/ web/src/test/java/org/apache/shiro/web/

Author: lhazlewood
Date: Fri Jul 24 23:19:11 2009
New Revision: 797689

URL: http://svn.apache.org/viewvc?rev=797689&view=rev
Log:
Fixed a few bugs related to remote proxy Session management (exceptions not properly propagating to the correct layer).  Added some performance enhancements in the DelegatingWebSecurityManager to cache the session on a thread-local instead of regularly accessing the back-end system avoiding network round-trips where possible.

Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/SessionListener.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DelegatingWebSessionManager.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/SessionListener.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/SessionListener.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/SessionListener.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/SessionListener.java Fri Jul 24 23:19:11 2009
@@ -19,7 +19,7 @@
 package org.apache.shiro.session;
 
 /**
- * Interface to be implemented by components that wish to be notified of events that occur during a 
+ * Interface to be implemented by components that wish to be notified of events that occur during a
  * {@link Session Session}'s lifecycle.
  *
  * @author Les Hazlewood

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java Fri Jul 24 23:19:11 2009
@@ -158,8 +158,9 @@
         return inet;
     }
 
-    private void assertNotNull(Session session, Serializable sessionId) throws UnknownSessionException {
+    private void ensureNotNull(Session session, Serializable sessionId) throws UnknownSessionException {
         if (session == null) {
+            onUnknownSession(sessionId);
             throw new UnknownSessionException(sessionId);
         }
     }
@@ -173,14 +174,13 @@
         InetAddress hostAddress = null;
         try {
             Session s = retrieveSession(sessionId);
-            assertNotNull(s, sessionId);
+            ensureNotNull(s, sessionId);
             // Save the host address in case the session will be invalidated.
             // We want to retain it in case it is needed for a replacement session
             hostAddress = getHostAddressFallback(s);
             validate(s);
             return s;
         } catch (InvalidSessionException ise) {
-            onInvalidSessionId(sessionId);
             if (!isAutoCreateWhenInvalid()) {
                 throw ise;
             }
@@ -193,9 +193,6 @@
         }
     }
 
-    protected void onInvalidSessionId(Serializable id) {
-    }
-
     /**
      * Looks up a session from the underlying data store based on the specified {@code sessionId}.
      *
@@ -246,6 +243,20 @@
         afterStopped(s);
     }
 
+    /**
+     * Notification callback for subclasses that occurs when a client attempts to reference the session with the
+     * specified ID, but there does not exist any session with that id.
+     * <p/>
+     * A common case of this ocurring is if the client's referenced session times out and is deleted before the next
+     * time they interact with the system (such as often occurs with stale session id cookies in an web environment).
+     * The next time they send a request with the stale session id, this method would be called.
+     *
+     * @param sessionId the session id used to try and reference the non-existent session.
+     * @since 1.0
+     */
+    public void onUnknownSession(Serializable sessionId) {
+    }
+
     protected void onExpiration(Session session) {
         onChange(session);
     }

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java Fri Jul 24 23:19:11 2009
@@ -54,6 +54,7 @@
     //cached fields to avoid a server-side method call if out-of-process:
     private Date startTimestamp = null;
     private InetAddress hostAddress = null;
+    private boolean handleReplacedSessions = true;
 
     /**
      * Handle to a server-side SessionManager.  See {@link #setSessionManager} for details.
@@ -69,6 +70,14 @@
         this.id = id;
     }
 
+    public DelegatingSession(SessionManager sessionManager, Serializable id,
+                             InetAddress hostAddress, boolean handleReplacedSessions) {
+        this.sessionManager = sessionManager;
+        this.id = id;
+        this.hostAddress = hostAddress;
+        this.handleReplacedSessions = handleReplacedSessions;
+    }
+
     /**
      * Returns the {@link SessionManager SessionManager} used by this handle to invoke
      * all session-related methods.
@@ -100,6 +109,14 @@
         this.sessionManager = sessionManager;
     }
 
+    public boolean isHandleReplacedSessions() {
+        return handleReplacedSessions;
+    }
+
+    public void setHandleReplacedSessions(boolean handleReplacedSessions) {
+        this.handleReplacedSessions = handleReplacedSessions;
+    }
+
     /**
      * Sets the sessionId used by this handle for all future {@link SessionManager SessionManager}
      * method invocations.
@@ -126,6 +143,10 @@
             try {
                 startTimestamp = sessionManager.getStartTimestamp(id);
             } catch (ReplacedSessionException e) {
+                if (!isHandleReplacedSessions()) {
+                    //propagate immediately
+                    throw e;
+                }
                 this.id = e.getNewSessionId();
                 startTimestamp = sessionManager.getStartTimestamp(id);
             }
@@ -141,6 +162,10 @@
         try {
             return sessionManager.getLastAccessTime(id);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             return sessionManager.getLastAccessTime(id);
         }
@@ -150,6 +175,10 @@
         try {
             return sessionManager.getTimeout(id);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             return sessionManager.getTimeout(id);
         }
@@ -159,6 +188,10 @@
         try {
             sessionManager.setTimeout(id, maxIdleTimeInMillis);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             sessionManager.setTimeout(id, maxIdleTimeInMillis);
         }
@@ -172,6 +205,10 @@
             try {
                 hostAddress = sessionManager.getHostAddress(id);
             } catch (ReplacedSessionException e) {
+                if (!isHandleReplacedSessions()) {
+                    //propagate immediately
+                    throw e;
+                }
                 this.id = e.getNewSessionId();
                 hostAddress = sessionManager.getHostAddress(id);
             }
@@ -186,6 +223,10 @@
         try {
             sessionManager.touch(id);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             // No need to 'hit' the session manager again - a newly created session is 'touched' at the time of creation
         }
@@ -198,6 +239,10 @@
         try {
             sessionManager.stop(id);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             //TODO - prevent sessionManager from creating new session when 'stop' is already requested.
             sessionManager.stop(id);
@@ -212,6 +257,10 @@
         try {
             return sessionManager.getAttributeKeys(id);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             // No need to 'hit' the session manager again - a new session won't have any attributes:
             return Collections.EMPTY_SET;
@@ -225,6 +274,10 @@
         try {
             return sessionManager.getAttribute(id, key);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             // No need to 'hit' the session manager again - a new session won't have any attributes
             return null;
@@ -241,6 +294,10 @@
             try {
                 sessionManager.setAttribute(id, key, value);
             } catch (ReplacedSessionException e) {
+                if (!isHandleReplacedSessions()) {
+                    //propagate immediately
+                    throw e;
+                }
                 this.id = e.getNewSessionId();
                 sessionManager.setAttribute(id, key, value);
             }
@@ -254,6 +311,10 @@
         try {
             return sessionManager.removeAttribute(id, key);
         } catch (ReplacedSessionException e) {
+            if (!isHandleReplacedSessions()) {
+                //propagate immediately
+                throw e;
+            }
             this.id = e.getNewSessionId();
             // No need to 'hit' the session manager again - a new session won't have any attributes:
             return null;

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java Fri Jul 24 23:19:11 2009
@@ -112,6 +112,10 @@
         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
     }
 
+    private void markSessionIdInvalid(ServletRequest request) {
+        request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
+    }
+
     private void removeSessionIdCookie(ServletRequest request, ServletResponse response) {
         getSessionIdCookieAttribute().removeValue(request, response);
     }
@@ -131,6 +135,9 @@
         }
         if (id != null) {
             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+            //automatically mark it valid here.  If it is invalid, the
+            //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
+            markSessionIdValid(id, request);
         }
         return id;
     }
@@ -154,6 +161,12 @@
         return getReferencedSessionId(request, response);
     }
 
+    @Override
+    public void onUnknownSession(Serializable sessionId) {
+        markSessionIdInvalid(WebUtils.getRequiredServletRequest());
+        removeSessionIdCookie();
+    }
+
     protected void onStop(Session session) {
         super.onStop(session);
         removeSessionIdCookie();

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DelegatingWebSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DelegatingWebSessionManager.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DelegatingWebSessionManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DelegatingWebSessionManager.java Fri Jul 24 23:19:11 2009
@@ -18,12 +18,20 @@
  */
 package org.apache.shiro.web.session;
 
+import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.session.InvalidSessionException;
 import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
 import org.apache.shiro.session.mgt.DelegatingSession;
 import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.util.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.Serializable;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.Date;
 import java.util.Map;
 
 /**
@@ -44,6 +52,8 @@
  */
 public class DelegatingWebSessionManager extends DefaultWebSessionManager {
 
+    private static transient final Logger log = LoggerFactory.getLogger(DelegatingWebSessionManager.class);
+
     private static final String THREAD_CONTEXT_SESSION_KEY =
             DelegatingWebSessionManager.class.getName() + ".THREAD_CONTEXT_SESSION_KEY";
 
@@ -55,11 +65,11 @@
 
     public DelegatingWebSessionManager(SessionManager delegateSessionManager) {
         this();
-        this.delegateSessionManager = delegateSessionManager;
+        this.delegateSessionManager = new ThreadClearingSessionManager(delegateSessionManager);
     }
 
     public void setDelegateSessionManager(SessionManager delegateSessionManager) {
-        this.delegateSessionManager = delegateSessionManager;
+        this.delegateSessionManager = new ThreadClearingSessionManager(delegateSessionManager);
     }
 
     private void assertDelegateExists() {
@@ -97,19 +107,29 @@
 
     @Override
     protected Session retrieveSessionFromDataSource(Serializable id) throws InvalidSessionException {
-        /*Session session = (Session)ThreadContext.get(THREAD_CONTEXT_SESSION_KEY);
-        if ( session != null ) {
+        //use thread-local caching to eliminate repeated 'hits' on the back-end data store during
+        //the thread execution.  We do this here and not in the parent class since we can ensure the
+        //ThreadContext is being cleared at the end of each request due to the ShiroFilter being required in web
+        //environments (which automatically clears the thread).
+        Session session = (Session) ThreadContext.get(THREAD_CONTEXT_SESSION_KEY);
+        if (session != null) {
+            log.trace("Returning thread-cached session.");
             return session;
-        }*/
+        }
         assertDelegateExists();
-        this.delegateSessionManager.checkValid(id);
-        return new DelegatingSession(this.delegateSessionManager, id);
-        /*//we need the DelegatingSession to reference the delegateSessionManager and not 'this' so
-        //we avoid an infinite loop:
-        session = new DelegatingSession(this.delegateSessionManager, id);
+        //get the host address and bind it to the thread.  This call will both validate the session as well as
+        //make it accessible for futher host checks:
+        InetAddress host = this.delegateSessionManager.getHostAddress(id);
+        session = new DelegatingSession(this.delegateSessionManager, id, host, false);
+        log.trace("Cached the session retrieved from the datasource in a thread-local for continued thread access.");
         ThreadContext.put(THREAD_CONTEXT_SESSION_KEY, session);
-        
-        return session;*/
+
+        return session;
+    }
+
+    protected void removeThreadBoundSession() {
+        log.debug("Session is invalid or an invalid id was encountered.  Unbinding the thread-cached session.");
+        ThreadContext.remove(THREAD_CONTEXT_SESSION_KEY);
     }
 
     @Override
@@ -119,14 +139,157 @@
 
     @Override
     protected void doValidate(Session session) throws InvalidSessionException {
-        /*if ( session == null ) {
-            throw new InvalidSessionException("Session method argument is null!" );
+        //do nothing - we rely on lazy session exceptions and recreation via the SessionManagerProxy to avoid
+        //costly validation checks on each session access.
+    }
+
+
+    private interface SessionManagerCallback {
+        Object doWithSessionManager(SessionManager sm) throws SessionException;
+    }
+
+    private class ThreadClearingSessionManager implements SessionManager {
+
+        private final SessionManager target;
+
+        private ThreadClearingSessionManager(SessionManager target) {
+            this.target = target;
         }
-        Serializable id = session.getId();
-        if ( id == null ) {
-            throw new InvalidSessionException("Session does not have an id!" );
+
+        private Object execute(SessionManagerCallback smc) throws SessionException {
+            try {
+                return smc.doWithSessionManager(target);
+            } catch (SessionException se) {
+                removeThreadBoundSession();
+                //propagate after cleanup:
+                throw se;
+            }
+        }
+
+        public Serializable start(final InetAddress originatingHost) throws AuthorizationException {
+            return (Serializable) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.start(originatingHost);
+                }
+            });
+        }
+
+        public Serializable start(final Map initData) throws AuthorizationException {
+            return (Serializable) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.start(initData);
+                }
+            });
+        }
+
+        public Date getStartTimestamp(final Serializable sessionId) {
+            return (Date) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getStartTimestamp(sessionId);
+                }
+            });
+        }
+
+        public Date getLastAccessTime(final Serializable sessionId) {
+            return (Date) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getLastAccessTime(sessionId);
+                }
+            });
+        }
+
+        public boolean isValid(final Serializable sessionId) {
+            return (Boolean) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.isValid(sessionId);
+                }
+            });
+        }
+
+        public void checkValid(final Serializable sessionId) throws InvalidSessionException {
+            execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    sm.checkValid(sessionId);
+                    return null;
+                }
+            });
+        }
+
+        public long getTimeout(final Serializable sessionId) throws InvalidSessionException {
+            return (Long) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getTimeout(sessionId);
+                }
+            });
+        }
+
+        public void setTimeout(final Serializable sessionId, final long maxIdleTimeInMillis) throws InvalidSessionException {
+            execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    sm.setTimeout(sessionId, maxIdleTimeInMillis);
+                    return null;
+                }
+            });
+        }
+
+        public void touch(final Serializable sessionId) throws InvalidSessionException {
+            execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    sm.touch(sessionId);
+                    return null;
+                }
+            });
+        }
+
+        public InetAddress getHostAddress(final Serializable sessionId) {
+            return (InetAddress) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getHostAddress(sessionId);
+                }
+            });
+        }
+
+        public void stop(final Serializable sessionId) throws InvalidSessionException {
+            execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    sm.stop(sessionId);
+                    return null;
+                }
+            });
+        }
+
+        @SuppressWarnings({"unchecked"})
+        public Collection<Object> getAttributeKeys(final Serializable sessionId) {
+            return (Collection<Object>) execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getAttributeKeys(sessionId);
+                }
+            });
+        }
+
+        public Object getAttribute(final Serializable sessionId, final Object key) throws InvalidSessionException {
+            return execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.getAttribute(sessionId, key);
+                }
+            });
+        }
+
+        public void setAttribute(final Serializable sessionId, final Object key, final Object value) throws InvalidSessionException {
+            execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    sm.setAttribute(sessionId, key, value);
+                    return null;
+                }
+            });
+        }
+
+        public Object removeAttribute(final Serializable sessionId, final Object key) throws InvalidSessionException {
+            return execute(new SessionManagerCallback() {
+                public Object doWithSessionManager(SessionManager sm) throws SessionException {
+                    return sm.removeAttribute(sessionId, key);
+                }
+            });
         }
-        assertDelegateExists();
-        this.delegateSessionManager.checkValid(id);*/
     }
 }

Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java?rev=797689&r1=797688&r2=797689&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java Fri Jul 24 23:19:11 2009
@@ -1,9 +1,10 @@
 package org.apache.shiro.web;
 
-import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.mgt.DefaultSecurityManager;
-import org.apache.shiro.realm.text.PropertiesRealm;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.ReplacedSessionException;
 import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.AbstractSessionManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.ThreadContext;
 import static org.easymock.EasyMock.*;
@@ -16,6 +17,10 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.UUID;
 
 /**
  * Unit test for the {@link org.apache.shiro.web.DelegatingWebSecurityManager} implementation.
@@ -24,23 +29,18 @@
  */
 public class DelegatingWebSecurityManagerTest {
 
-    private DefaultSecurityManager delegate;
     private DelegatingWebSecurityManager sm;
 
     @Before
     public void setup() {
-        delegate = new DefaultSecurityManager();
-        delegate.setRealm(new PropertiesRealm());
-        sm = new DelegatingWebSecurityManager();
-        sm.setDelegateSecurityManager(delegate);
-        SecurityUtils.setSecurityManager(sm);
         ThreadContext.clear();
+        sm = new DelegatingWebSecurityManager();
+        ThreadContext.bind(sm);
     }
 
     @After
     public void tearDown() {
         sm.destroy();
-        delegate.destroy();
         ThreadContext.clear();
     }
 
@@ -54,8 +54,9 @@
 
     @Test
     public void testSessionTimeout() {
-        long globalTimeout = 100;
-        delegate.setGlobalSessionTimeout(globalTimeout);
+
+        SecurityManager delegate = createMock(SecurityManager.class);
+        sm.setDelegateSecurityManager(delegate);
 
         HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
         WebUtils.bind(mockRequest);
@@ -65,18 +66,48 @@
         expect(mockRequest.getCookies()).andReturn(null);
         expect(mockRequest.getContextPath()).andReturn("/");
 
+        InetAddress host;
+        try {
+            host = InetAddress.getByName("192.168.1.1");
+        } catch (UnknownHostException e) {
+            throw new IllegalStateException(e);
+        }
+
+        Serializable sessionId = UUID.randomUUID().toString();
+        expect(delegate.start((Map) null)).andReturn(sessionId);
+        expect(delegate.getHostAddress(sessionId)).andReturn(host);
+        expect(delegate.getTimeout(sessionId)).andReturn(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
+        delegate.setTimeout(sessionId, 125);
+        expectLastCall().times(1);
+        expect(delegate.getTimeout(sessionId)).andReturn(125L);
+        //pretend that 125ms have gone by
+        Serializable replacedSessionId = UUID.randomUUID().toString();
+        @SuppressWarnings({"ThrowableInstanceNeverThrown"})
+        ReplacedSessionException replaced =
+                new ReplacedSessionException("test", new ExpiredSessionException(sessionId),
+                        sessionId, replacedSessionId);
+        expect(delegate.getTimeout(sessionId)).andThrow(replaced);
+        //the DelegatingSession will re-try the call on a ReplacedSessionException
+        expect(delegate.getHostAddress(replacedSessionId)).andReturn(host);
+        expect(delegate.getTimeout(replacedSessionId)).andReturn(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
+
+        replay(delegate);
         replay(mockRequest);
 
         Subject subject = sm.getSubject();
         Session session = subject.getSession();
-        Serializable origId = session.getId();
-        assertEquals(globalTimeout, session.getTimeout());
+        String id = session.getId().toString();
+        assertEquals(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT, session.getTimeout());
         session.setTimeout(125);
         assertEquals(125, session.getTimeout());
-        sleep(175);
+        //sleep(175);
         //now the underlying session should have been expired and a new one replaced by default.
         //so ensure the replaced session has the default session timeout:
-        assertEquals(globalTimeout, session.getTimeout());
-        assertFalse(origId.equals(session.getId())); //new ID would have been generated
+        long timeout = session.getTimeout();
+        assertEquals(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT, timeout);
+        assertFalse(id.equals(session.getId())); //new ID would have been generated
+
+        verify(delegate);
+        verify(mockRequest);
     }
 }