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/22 21:02:50 UTC
svn commit: r796832 - in /incubator/shiro/trunk:
core/src/main/java/org/apache/shiro/mgt/
core/src/main/java/org/apache/shiro/session/
core/src/main/java/org/apache/shiro/session/mgt/
core/src/main/java/org/apache/shiro/session/mgt/eis/ core/src/main/j...
Author: lhazlewood
Date: Wed Jul 22 19:02:49 2009
New Revision: 796832
URL: http://svn.apache.org/viewvc?rev=796832&view=rev
Log:
- SHIRO-53 - enabled auto-deletion of Session objects after invalidation to prevent orphans. Added DefaultSessionManagerTest#testSessionDeleteOnExpiration() testcase for verification.
- Removed auto-deletion of cached objects from CachingSessionDAO - this was preventing the SessionManager from performing appropriate cleanup/notification logic necessary for any invalid sessions held by the cache (this was preventing SessionListeners from receiving onExpiration and onStop callbacks)
- Added proper toString, equals and hashCode implementations to SimpleSession
Modified:
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.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/DefaultSessionManager.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java Wed Jul 22 19:02:49 2009
@@ -509,7 +509,7 @@
* @see org.apache.shiro.authz.HostUnauthorizedException
* @since 1.0
*/
- private Subject getSubjectBySessionId(Serializable sessionId) throws InvalidSessionException, AuthorizationException {
+ protected Subject getSubjectBySessionId(Serializable sessionId) throws InvalidSessionException, AuthorizationException {
Session session = getSession(sessionId);
Map<String, Object> context = new HashMap<String, Object>(1);
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java Wed Jul 22 19:02:49 2009
@@ -128,9 +128,7 @@
*/
Subject getSubject(Map context);
- /*Subject getSubject(Map initData);
+ //Subject getSubjectBySessionId(Serializable sessionId);
- Subject getSubjectBySessionId(Serializable sessionId);
-
- Subject getSubject(PrincipalCollection principals);*/
+ //Subject getSubject(PrincipalCollection principals);
}
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java Wed Jul 22 19:02:49 2009
@@ -71,7 +71,9 @@
* @param sessionId the session id given that is unknown to the system.
*/
public UnknownSessionException(Serializable sessionId) {
- super(sessionId);
+ super("Unable to locate session with id [" + sessionId + "] either because it is an invalid id " +
+ "or the session has been deleted due to invalidation (stopped, logged out, or expired).",
+ sessionId);
}
/**
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java Wed Jul 22 19:02:49 2009
@@ -143,7 +143,7 @@
* <em>after</em> the {@link #onStart(org.apache.shiro.session.Session)} method is called.
*
* @param session the session that has just started that will be delivered to any
- * {@link #setSessionListeners(java.util.Collection) registered} session listeners.
+ * {@link #setSessionListeners(java.util.Collection) registered} session listeners.
* @see SessionListener#onStart(org.apache.shiro.session.Session)
*/
protected void notifyStart(Session session) {
@@ -206,6 +206,10 @@
session.stop();
onStop(session);
notifyStop(session);
+ afterStopped(session);
+ }
+
+ protected void afterStopped(Session session) {
}
public Collection<Object> getAttributeKeys(Serializable sessionId) {
@@ -236,6 +240,9 @@
}
protected Session getSession(Serializable sessionId) throws InvalidSessionException {
+ if (sessionId == null) {
+ throw new IllegalArgumentException("sessionId parameter cannot be null.");
+ }
Session session = doGetSession(sessionId);
if (session == null) {
String msg = "There is no session with id [" + sessionId + "]";
@@ -272,10 +279,6 @@
onChange(session);
}
- protected void onExpiration(Session session) {
- onChange(session);
- }
-
protected void onChange(Session s) {
}
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=796832&r1=796831&r2=796832&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 Wed Jul 22 19:02:49 2009
@@ -19,10 +19,7 @@
package org.apache.shiro.session.mgt;
import org.apache.shiro.authz.AuthorizationException;
-import org.apache.shiro.session.ExpiredSessionException;
-import org.apache.shiro.session.InvalidSessionException;
-import org.apache.shiro.session.ReplacedSessionException;
-import org.apache.shiro.session.Session;
+import org.apache.shiro.session.*;
import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.LifecycleUtils;
import org.apache.shiro.util.ThreadContext;
@@ -152,7 +149,22 @@
this.autoCreateWhenInvalid = autoCreateWhenInvalid;
}
- protected final Session doGetSession(Serializable sessionId) throws InvalidSessionException {
+ private InetAddress getHostAddressFallback(Session s) {
+ InetAddress inet = s.getHostAddress();
+ if (inet == null) {
+ //fallback to thread local just in case:
+ inet = ThreadContext.getInetAddress();
+ }
+ return inet;
+ }
+
+ private static void assertNotNull(Session session, Serializable sessionId) throws UnknownSessionException {
+ if (session == null) {
+ throw new UnknownSessionException(sessionId);
+ }
+ }
+
+ protected final Session doGetSession(final Serializable sessionId) throws InvalidSessionException {
enableSessionValidationIfNecessary();
if (log.isTraceEnabled()) {
@@ -161,35 +173,31 @@
InetAddress hostAddress = null;
try {
Session s = retrieveSession(sessionId);
- //save the host address in case the session will be invalidated. We want to retain it for the
- //replacement session:
- hostAddress = s.getHostAddress();
+ assertNotNull(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) {
- if (isAutoCreateWhenInvalid()) {
- if (hostAddress == null) {
- //try the threadContext as a last resort:
- hostAddress = ThreadContext.getInetAddress();
- }
- Serializable newId = start(hostAddress);
- String msg = "Session with id [" + sessionId + "] is invalid. The SessionManager " +
- "has been configured to automatically re-create sessions upon invalidation. Returnining " +
- "new session id [" + newId + "] with exception so the caller may react accordingly.";
- throw new ReplacedSessionException(msg, ise, sessionId, newId);
- } else {
- //propagate original exception:
+ if (!isAutoCreateWhenInvalid()) {
throw ise;
}
+ //otherwise auto-create a new session and indicate via a ReplacedSessionException
+ Serializable newId = start(hostAddress);
+ String msg = "Session with id [" + sessionId + "] is invalid. The SessionManager " +
+ "has been configured to automatically re-create sessions upon invalidation. Returnining " +
+ "new session id [" + newId + "] with exception so the caller may react accordingly.";
+ throw new ReplacedSessionException(msg, ise, sessionId, newId);
}
}
/**
* Looks up a session from the underlying data store based on the specified {@code sessionId}.
*
- * @param sessionId
- * @return
- * @throws InvalidSessionException
+ * @param sessionId the id of the session to retrieve from the data store
+ * @return the session identified by {@code sessionId}.
+ * @throws InvalidSessionException if there is no session identified by {@code sessionId}.
*/
protected abstract Session retrieveSession(Serializable sessionId) throws InvalidSessionException;
@@ -203,12 +211,36 @@
protected void validate(Session session) throws InvalidSessionException {
try {
doValidate(session);
- } catch (ExpiredSessionException ese) {
- onExpiration(session);
- notifyExpiration(session);
- //propagate to caller:
- throw ese;
+ } catch (InvalidSessionException ise) {
+ onInvalidation(session, ise);
+ throw ise;
+ }
+ }
+
+ protected void onInvalidation(Session session, InvalidSessionException ise) {
+ if (ise instanceof ExpiredSessionException) {
+ onExpiration(session, (ExpiredSessionException) ise);
+ return;
}
+ if (log.isDebugEnabled()) {
+ log.debug("Session with id [" + session.getId() + "] is invalid.");
+ }
+ onStop(session);
+ notifyStop(session);
+ afterStopped(session);
+ }
+
+ protected void onExpiration(Session s, ExpiredSessionException ese) {
+ onExpiration(s);
+ notifyExpiration(s);
+ afterExpired(s);
+ }
+
+ protected void onExpiration(Session session) {
+ onChange(session);
+ }
+
+ protected void afterExpired(Session session) {
}
protected void doValidate(Session session) throws InvalidSessionException {
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java Wed Jul 22 19:02:49 2009
@@ -50,7 +50,9 @@
private SessionFactory sessionFactory;
- protected SessionDAO sessionDAO;
+ protected SessionDAO sessionDAO; //todo - move SessionDAO up to AbstractValidatingSessionManager?
+
+ private boolean deleteInvalidSessions = true;
public DefaultSessionManager() {
this.sessionFactory = new SimpleSessionFactory();
@@ -87,6 +89,44 @@
this.sessionFactory = sessionFactory;
}
+ /**
+ * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+ * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control. The
+ * default is {@code true} to ensure no orphans exist in the underlying data store.
+ * <h4>Usage</h4>
+ * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
+ * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
+ * job. If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
+ * <p/>
+ * This property is provided because some systems need the ability to perform querying/reporting against sessions in
+ * the data store, even after they have stopped or expired. Setting this attribute to {@code false} will allow
+ * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
+ * some other means (cron, quartz, etc).
+ *
+ * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+ * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
+ * @since 1.0
+ */
+ public boolean isDeleteInvalidSessions() {
+ return deleteInvalidSessions;
+ }
+
+ /**
+ * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid. Default
+ * value is {@code true} to ensure no orphans will exist in the underlying data store.
+ * <h4>WARNING</h4>
+ * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
+ * (quartz, cron, etc) external to Shiro's control. See the
+ * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
+ *
+ * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
+ * to be invalid.
+ * @since 1.0
+ */
+ public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
+ this.deleteInvalidSessions = deleteInvalidSessions;
+ }
+
public void setCacheManager(CacheManager cacheManager) {
if (this.sessionDAO instanceof CacheManagerAware) {
((CacheManagerAware) this.sessionDAO).setCacheManager(cacheManager);
@@ -129,6 +169,13 @@
onChange(session);
}
+ @Override
+ protected void afterStopped(Session session) {
+ if (isDeleteInvalidSessions()) {
+ delete(session);
+ }
+ }
+
protected void onExpiration(Session session) {
if (session instanceof SimpleSession) {
((SimpleSession) session).setExpired(true);
@@ -136,6 +183,13 @@
onChange(session);
}
+ @Override
+ protected void afterExpired(Session session) {
+ if (isDeleteInvalidSessions()) {
+ delete(session);
+ }
+ }
+
protected void onChange(Session session) {
sessionDAO.update(session);
}
@@ -151,6 +205,10 @@
return sessionDAO.readSession(sessionId);
}
+ protected void delete(Session session) {
+ sessionDAO.delete(session);
+ }
+
protected Collection<Session> getActiveSessions() {
Collection<Session> active = sessionDAO.getActiveSessions();
return active != null ? active : CollectionUtils.emptyCollection(Session.class);
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java Wed Jul 22 19:02:49 2009
@@ -86,7 +86,7 @@
/**
* Returns the time the session was stopped, or <tt>null</tt> if the session is still active.
* <p/>
- * <p>A session may become stopped under a number of conditions:
+ * A session may become stopped under a number of conditions:
* <ul>
* <li>If the user logs out of the system, their current session is terminated (released).</li>
* <li>If the session expires</li>
@@ -94,9 +94,8 @@
* <li>If there is an internal system error and the session state can no longer accurately
* reflect the user's behavior, such in the case of a system crash</li>
* </ul>
- * </p>
* <p/>
- * <p>Once stopped, a session may no longer be used. It is locked from all further activity.
+ * Once stopped, a session may no longer be used. It is locked from all further activity.
*
* @return The time the session was stopped, or <tt>null</tt> if the session is still
* active.
@@ -303,4 +302,92 @@
}
}
+ /**
+ * Returns {@code true} if the specified argument is an {@code instanceof} {@code SimpleSession} and both
+ * {@link #getId() id}s are equal. If the argument is a {@code SimpleSession} and either 'this' or the argument
+ * does not yet have an ID assigned, the value of {@link #onEquals(SimpleSession) onEquals} is returned, which
+ * does a necessary attribute-based comparison when IDs are not available.
+ * <p/>
+ * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+ * avoid the more expensive attributes-based comparison.
+ *
+ * @param obj the object to compare with this one for equality.
+ * @return {@code true} if this object is equivalent to the specified argument, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof SimpleSession) {
+ SimpleSession other = (SimpleSession) obj;
+ Serializable thisId = getId();
+ Serializable otherId = other.getId();
+ if (thisId != null && otherId != null) {
+ return thisId.equals(otherId);
+ } else {
+ //fall back to an attribute based comparison:
+ return onEquals(other);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Provides an attribute-based comparison (no ID comparison) - incurred <em>only</em> when 'this' or the
+ * session object being compared for equality do not have a session id.
+ *
+ * @param ss the SimpleSession instance to compare for equality.
+ * @return true if all the attributes, except the id, are equal to this object's attributes.
+ * @since 1.0
+ */
+ protected boolean onEquals(SimpleSession ss) {
+ return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null) &&
+ (getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null) &&
+ (getLastAccessTime() != null ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null) &&
+ (getTimeout() == ss.getTimeout()) &&
+ (isExpired() == ss.isExpired()) &&
+ (getHostAddress() != null ? getHostAddress().equals(ss.getHostAddress()) : ss.getHostAddress() == null) &&
+ (getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
+ }
+
+ /**
+ * Returns the hashCode. If the {@link #getId() id} is not {@code null}, its hashcode is returned immediately.
+ * If it is {@code null}, an attributes-based hashCode will be calculated and returned.
+ * <p/>
+ * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+ * avoid the more expensive attributes-based calculation.
+ *
+ * @return this object's hashCode
+ * @since 1.0
+ */
+ @Override
+ public int hashCode() {
+ if (getId() != null) {
+ return getId().hashCode();
+ }
+ int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
+ hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
+ hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
+ hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
+ hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
+ hashCode = 31 * hashCode + (getHostAddress() != null ? getHostAddress().hashCode() : 0);
+ hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
+ return hashCode;
+ }
+
+ /**
+ * Returns the string representation of this SimpleSession, equal to
+ * <code>getClass().getName() + ",id=" + getId()</code>.
+ *
+ * @return the string representation of this SimpleSession, equal to
+ * <code>getClass().getName() + ",id=" + getId()</code>.
+ * @since 1.0
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getName()).append(",id=").append(getId());
+ return sb.toString();
+ }
}
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java Wed Jul 22 19:02:49 2009
@@ -18,22 +18,21 @@
*/
package org.apache.shiro.session.mgt.eis;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.CacheManagerAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
-import org.apache.shiro.session.mgt.ValidatingSession;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
/**
* An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
* use it and the underlying EIS (Enterprise Information System) for enhanced performance.
- *
+ * <p/>
* <p>This implementation caches all active sessions in a cache created by a
* {@link org.apache.shiro.cache.CacheManager}. All <tt>SessionDAO</tt> methods are implemented by this class to employ
* caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
@@ -93,6 +92,7 @@
/**
* Returns the name of the actives sessions cache to be returned by the <code>CacheManager</code>. Unless
* overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
+ *
* @return the name of the active sessions cache.
*/
public String getActiveSessionsCacheName() {
@@ -102,7 +102,8 @@
/**
* Sets the name of the active sessions cache to be returned by the <code>CacheManager</code>. Defaults to
* {@link #ACTIVE_SESSION_CACHE_NAME}.
- * @param activeSessionsCacheName the name of the active sessions cache to be returned by the <code>CacheManager</code>.
+ *
+ * @param activeSessionsCacheName the name of the active sessions cache to be returned by the <code>CacheManager</code>.
*/
public void setActiveSessionsCacheName(String activeSessionsCacheName) {
this.activeSessionsCacheName = activeSessionsCacheName;
@@ -110,6 +111,7 @@
/**
* Returns the cache instance to use for storing active sessions.
+ *
* @return the cache instance to use for storing active sessions.
*/
public Cache getActiveSessionsCache() {
@@ -122,7 +124,7 @@
* <p/>
* Note that this method will only return a non-null value code if the <code>CacheManager</code> has been set. If
* not set, there will be no cache.
- *
+ *
* @return the active sessions cache instance.
*/
protected Cache getActiveSessionsCacheLazy() {
@@ -134,6 +136,7 @@
/**
* Sets the cache instance to use for storing active sessions.
+ *
* @param cache the cache instance to use for storing active sessions.
*/
public void setActiveSessionsCache(Cache cache) {
@@ -146,8 +149,9 @@
* cache returned is that resulting from the following call:
* <pre> String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
* cacheManager.getCache(name);</pre>
+ *
* @return a cache instance used to store active sessions, or <em>null</code> if the <code>CacheManager</code> has
- * not been set.
+ * not been set.
*/
protected Cache createActiveSessionsCache() {
Cache cache = null;
@@ -168,7 +172,7 @@
public Serializable create(Session session) {
Serializable sessionId = doCreate(session);
verifySessionId(sessionId);
- cacheValidSession(session, sessionId);
+ cache(session, sessionId);
return sessionId;
}
@@ -178,7 +182,7 @@
*
* @param sessionId the id of the cached session to acquire.
* @return the cached session with the corresponding <code>sessionId</code>, or <code>null</code> if the session
- * does not exist or is not cached.
+ * does not exist or is not cached.
*/
protected Session getCachedSession(Serializable sessionId) {
Session cached = null;
@@ -194,8 +198,9 @@
/**
* Returns the Session with the specified id from the specified cache. This method simply calls
* <code>cache.get(sessionId)</code> and can be overridden by subclasses for custom acquisition behavior.
+ *
* @param sessionId the id of the session to acquire.
- * @param cache the cache to acquire the session from
+ * @param cache the cache to acquire the session from
* @return the cached session, or <code>null</code> if the session wasn't in the cache.
*/
protected Session getCachedSession(Serializable sessionId, Cache cache) {
@@ -203,37 +208,30 @@
}
/**
- * Caches the specified session under the key <code>sessionId</code>. If the Session is an instance of
- * {@link org.apache.shiro.session.mgt.ValidatingSession ValidatingSession}, it will only be cached if the
- * session is {@link org.apache.shiro.session.mgt.ValidatingSession#isValid() valid}.
+ * Caches the specified session under the cache entry key of {@code sessionId}.
*
- * @param session the session to cache
- * @param sessionId the id of the session, to be used as the cache key.
+ * @param session the session to cache
+ * @param sessionId the session id, to be used as the cache entry key.
+ * @since 1.0
*/
- protected void cacheValidSession(Session session, Serializable sessionId) {
+ protected void cache(Session session, Serializable sessionId) {
if (session == null || sessionId == null) {
return;
}
-
Cache cache = getActiveSessionsCacheLazy();
if (cache == null) {
return;
}
-
- if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
- uncache(session);
- } else {
- cache(session, sessionId, cache);
- }
+ cache(session, sessionId, cache);
}
/**
* Caches the specified session in the given cache under the key of <code>sessionId</code>. This implementation
* simply calls <code>cache.put(sessionId, session)</code> and can be overridden for custom behavior.
- *
- * @param session the session to cache
+ *
+ * @param session the session to cache
* @param sessionId the id of the session, expected to be the cache key.
- * @param cache the cache to store the session
+ * @param cache the cache to store the session
*/
protected void cache(Session session, Serializable sessionId, Cache cache) {
cache.put(sessionId, session);
@@ -250,22 +248,6 @@
String msg = "sessionId returned from doCreate implementation is null. Please verify the implementation.";
throw new IllegalStateException(msg);
}
- ensureUncached(sessionId);
- }
-
- /**
- * Ensures that there is no cache entry already in place for a session with id of <tt>sessionId</tt>. Used by
- * the {@link #verifySessionId} implementation.
- *
- * @param sessionId the session id to check for non-existence in the cache.
- */
- protected void ensureUncached(Serializable sessionId) {
- Cache cache = getActiveSessionsCacheLazy();
- if (cache != null && cache.get(sessionId) != null) {
- String msg = "There is an existing session already created with session id [" +
- sessionId + "]. Session ID's must be unique.";
- throw new IllegalArgumentException(msg);
- }
}
/**
@@ -279,7 +261,7 @@
/**
* Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt>.
- *
+ * <p/>
* <p>Upon receiving the Session object from the subclass's {@link #doReadSession} implementation, it will be
* cached first and then returned to the caller.
*
@@ -293,9 +275,6 @@
if (s == null) {
s = doReadSession(sessionId);
- if (s != null) {
- cacheValidSession(s, sessionId);
- }
}
if (s == null) {
@@ -314,13 +293,13 @@
/**
* Updates the state of the given session to the EIS.
- *
+ * <p/>
* <p>If the specified session was previously cached, and the session is now invalid,
* it will be removed from the cache.
- *
+ * <p/>
* <p>If the specified session is not stopped or expired, and was not yet in the cache, it will be added to the
* cache.
- *
+ * <p/>
* <p>Finally, this method calls {@link #doUpdate} for the subclass to actually push the object state to the EIS.
*
* @param session the session object to update in the EIS.
@@ -329,7 +308,7 @@
*/
public void update(Session session) throws UnknownSessionException {
doUpdate(session);
- cacheValidSession(session, session.getId());
+ cache(session, session.getId());
}
/**
@@ -346,8 +325,8 @@
* @param session the session to remove from caches and permanently delete from the EIS.
*/
public void delete(Session session) {
- doDelete(session);
uncache(session);
+ doDelete(session);
}
/**
@@ -378,7 +357,7 @@
/**
* Returns all active sessions in the system.
- *
+ * <p/>
* <p>This implementation merely returns the sessions found in the activeSessions cache. Subclass implementations
* may wish to override this method to retrieve them in a different way, perhaps by an RDBMS query or by other
* means.
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java Wed Jul 22 19:02:49 2009
@@ -18,32 +18,31 @@
*/
package org.apache.shiro.session.mgt.eis;
-import java.io.Serializable;
-import java.util.Random;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import org.apache.shiro.cache.HashtableCacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.util.JavaEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Random;
/**
* Simple memory-based implementation of the SessionDAO that relies on its configured
* {@link #setCacheManager CacheManager} for Session caching and in-memory persistence.
- *
+ * <p/>
* <p><b>PLEASE NOTE</b> the default CacheManager internal to this implementation is a
* {@link org.apache.shiro.cache.HashtableCacheManager HashtableCacheManager}, which IS NOT RECOMMENDED for production environments.
- *
+ * <p/>
* <p>If you
* want to use the MemorySessionDAO in production environments, such as those that require session data to be
* recoverable in case of a server restart, you should do one of two things (or both):
- *
+ * <p/>
* <ul>
* <li>Configure it with a production-quality CacheManager. The
- * {@link org.apache.shiro.cache.ehcache.EhCacheManager EhCacheManager} is one such implementation. It is not used by default
+ * {@code org.apache.shiro.cache.ehcache.EhCacheManager} is one such implementation. It is not used by default
* to prevent a forced runtime dependency on ehcache.jar that may not be required in many environments)</li><br/>
* <li>If you need session information beyond their transient start/stop lifetimes, you should subclass this one and
* override the <tt>do*</tt> methods to perform CRUD operations using an EIS-tier API (e.g. Hibernate/JPA/JCR/etc).
Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java Wed Jul 22 19:02:49 2009
@@ -180,14 +180,14 @@
protected void assertAuthzCheckPossible() throws AuthorizationException {
if (!hasPrincipals()) {
- String msg = "Identity principals are not associated with this Subject instance - " +
+ String msg = "This subject is anonymous - it does not have any identifying principals associated, and " +
"authorization operations require an identity to check against. A Subject instance will " +
"acquire these identifying principals automatically after a successful login is performed " +
"be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
- "functionality is enabled. This exception can also occur when the current Subject has logged out, " +
- "which relinquishes its identity and essentially makes it anonymous again. " +
- "Because an identity is currently not known due to any of these conditions, " +
- "authorization is denied.";
+ "functionality is enabled by the SecurityManager. This exception can also occur when a " +
+ "previously logged-in Subject has logged out, which relinquishes its identity and essentially " +
+ "makes it anonymous again. Because an identity is currently not known due to any of these " +
+ "conditions, authorization is denied.";
throw new UnauthenticatedException(msg);
}
}
@@ -294,7 +294,7 @@
this.session = null;
this.principals = null;
this.authenticated = false;
- //Don't set securityManager to null here - the Subject can be continued to be
+ //Don't set securityManager to null here - the Subject can still be
//used, it is just considered anonymous at this point. The SecurityManager instance is
//necessary if the subject would log in again or acquire a new session. This is in response to
//https://issues.apache.org/jira/browse/JSEC-22
Modified: incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java (original)
+++ incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java Wed Jul 22 19:02:49 2009
@@ -18,15 +18,20 @@
*/
package org.apache.shiro.session.mgt;
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.util.ThreadContext;
+import static org.easymock.EasyMock.*;
import org.junit.After;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import java.io.Serializable;
import java.net.InetAddress;
+import java.util.Map;
+import java.util.UUID;
/**
* Unit test for the {@link DefaultSessionManager DefaultSessionManager} implementation.
@@ -63,4 +68,59 @@
sleep(150);
assertFalse(sm.isValid(sessionId));
}
+
+ @Test
+ public void testSessionDeleteOnExpiration() {
+
+ sm.setAutoCreateWhenInvalid(false);
+ sm.setGlobalSessionTimeout(100);
+
+ SessionDAO sessionDAO = createMock(SessionDAO.class);
+ sm.setSessionDAO(sessionDAO);
+
+ String sessionId1 = UUID.randomUUID().toString();
+ final SimpleSession session1 = new SimpleSession();
+ session1.setId(sessionId1);
+ System.out.println("Session id 1: " + sessionId1);
+
+ final Session[] activeSession = new SimpleSession[]{session1};
+ sm.setSessionFactory(new SessionFactory() {
+ public Session createSession(Map initData) {
+ return activeSession[0];
+ }
+ });
+
+ expect(sessionDAO.create(eq(session1))).andReturn(sessionId1);
+ sessionDAO.update(eq(session1));
+ expectLastCall().anyTimes();
+ replay(sessionDAO);
+ Serializable id = sm.start((InetAddress) null);
+ assertNotNull(id);
+ verify(sessionDAO);
+ reset(sessionDAO);
+
+ expect(sessionDAO.readSession(sessionId1)).andReturn(session1).anyTimes();
+ sessionDAO.update(eq(session1));
+ replay(sessionDAO);
+ sm.setTimeout(sessionId1, 1);
+ verify(sessionDAO);
+ reset(sessionDAO);
+
+ sleep(20);
+
+ expect(sessionDAO.readSession(sessionId1)).andReturn(session1);
+ sessionDAO.update(eq(session1)); //update's the stop timestamp
+ sessionDAO.delete(session1);
+ replay(sessionDAO);
+
+ //Try to access the same session, but it should throw an UnknownSessionException due to timeout:
+ try {
+ sm.getTimeout(sessionId1);
+ fail("Session with id [" + sessionId1 + "] should have expired due to timeout.");
+ } catch (ExpiredSessionException expected) {
+ //expected
+ }
+
+ verify(sessionDAO); //verify that the delete call was actually made on the DAO
+ }
}
Modified: incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java (original)
+++ incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java Wed Jul 22 19:02:49 2009
@@ -63,7 +63,7 @@
Serializable origId = session.getId();
assertEquals(session.getTimeout(), AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
session.setTimeout(100);
- assertEquals(100, session.getTimeout());
+ assertEquals(session.getTimeout(), 100);
sleep(150);
//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:
Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java Wed Jul 22 19:02:49 2009
@@ -18,19 +18,15 @@
*/
package org.apache.shiro.web;
-import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.session.Session;
-import org.apache.shiro.session.mgt.AbstractSessionManager;
import org.apache.shiro.subject.Subject;
-import org.apache.shiro.authc.UsernamePasswordToken;
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.SimpleAuthenticationInfo;
-import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.util.ThreadContext;
+import static org.easymock.EasyMock.*;
import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.*;
-import static org.easymock.EasyMock.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -96,8 +92,6 @@
//so ensure the replaced session has the default session timeout:
assertEquals(session.getTimeout(), globalTimeout);
assertFalse(origId.equals(session.getId())); //new ID would have been generated
-
- //verify(mockRequest);
}
}