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 2012/06/18 04:01:12 UTC
svn commit: r1351193 [2/2] - in /shiro/branches/SHIRO-317b:
core/src/main/java/org/apache/shiro/event/
core/src/main/java/org/apache/shiro/session/event/
core/src/main/java/org/apache/shiro/session/mgt/
core/src/main/java/org/apache/shiro/session/mgt/e...
Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/StandardWebSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/StandardWebSessionManager.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/StandardWebSessionManager.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/StandardWebSessionManager.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,386 @@
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.event.Subscriber;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.*;
+import org.apache.shiro.util.Assert;
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+import org.apache.shiro.web.event.EndServletRequestEvent;
+import org.apache.shiro.web.servlet.*;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @since 1.3
+ */
+public class StandardWebSessionManager extends StandardSessionManager implements WebSessionManager, Subscriber {
+
+ private static final Logger log = LoggerFactory.getLogger(StandardWebSessionManager.class);
+
+ private static final String REQUEST_ID_ATTR_NAME = "ApacheShiroRequestId";
+
+ private static final String REFERENCED_SESSION_IDS = DefaultWebSessionManager.class.getName() + ".REFERENCED_SESSION_IDS";
+
+ private Cookie sessionIdCookie;
+ private boolean sessionIdCookieEnabled;
+ private AccessTimestampEvaluator accessTimestampEvaluator;
+ private RequestIdGenerator requestIdGenerator;
+
+
+ // The sessions actively referenced by this particular SessionManager node. Sessions are only present in this
+ // collection if they are being currently referenced by active requests serviced by this particular node.
+ //
+ // This collection effectively acts as a first-level cache in front of the SessionDAO: Session state changes are
+ // persisted to the SessionDAO only when:
+ // 1. there are no requests currently associated with the session and
+ // 2. when the last referencing request completes.
+ //
+ // This alleviates the potential constant 'hit' on SessionDAO back-end data stores that might not support
+ // first-level caching themselves.
+ //
+ // The SessionDAO is used for durable storage.
+ //
+ // key: session id, value: currently-in-use-session
+ private final ConcurrentMap<Serializable, Session> activeSessions;
+
+ //The number of requests interacting with a particular session in this node. When the number of references
+ //is 0, the count is removed from this collection to ensure memory remains reasonable.
+ //key: sessionID, value: total count of all requests currently interacting with the session identified by sessionId
+ private final ConcurrentMap<Serializable, AtomicInteger> activeSessionRequestCounts;
+
+ public StandardWebSessionManager() {
+ super();
+ Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+ cookie.setHttpOnly(true); //more secure, protects against XSS attacks
+ this.sessionIdCookie = cookie;
+ this.sessionIdCookieEnabled = true;
+ this.accessTimestampEvaluator = new SpecCompliantAccessTimestampEvaluator();
+ this.requestIdGenerator = new UuidRequestIdGenerator();
+ this.activeSessions = new ConcurrentHashMap<Serializable, Session>();
+ this.activeSessionRequestCounts = new ConcurrentHashMap<Serializable, AtomicInteger>();
+ }
+
+ public Cookie getSessionIdCookie() {
+ return sessionIdCookie;
+ }
+
+ public boolean isSessionIdCookieEnabled() {
+ return sessionIdCookieEnabled;
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public void setSessionIdCookie(Cookie sessionIdCookie) {
+ this.sessionIdCookie = sessionIdCookie;
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) {
+ this.sessionIdCookieEnabled = sessionIdCookieEnabled;
+ }
+
+ public AccessTimestampEvaluator getAccessTimestampEvaluator() {
+ return accessTimestampEvaluator;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setAccessTimestampEvaluator(AccessTimestampEvaluator accessTimestampEvaluator) {
+ this.accessTimestampEvaluator = accessTimestampEvaluator;
+ }
+
+ public RequestIdGenerator getRequestIdGenerator() {
+ return requestIdGenerator;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setRequestIdGenerator(RequestIdGenerator requestIdGenerator) {
+ this.requestIdGenerator = requestIdGenerator;
+ }
+
+ private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
+ Assert.notNull(currentId, "sessionId argument cannot be null.");
+
+ Cookie template = getSessionIdCookie();
+ Cookie cookie = new SimpleCookie(template);
+
+ String id = String.valueOf(currentId);
+ cookie.setValue(id);
+ cookie.saveTo(request, response);
+
+ log.trace("Set session ID cookie for session with id {}", id);
+ }
+
+ private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
+ if (isSessionIdCookieEnabled() && request instanceof HttpServletRequest) {
+ return getSessionIdCookie().readValue(WebUtils.toHttp(request), WebUtils.toHttp(response));
+ }
+ return null;
+ }
+
+ @Override
+ protected Session createExposedSession(Session session, Object httpPair) {
+ if (!WebUtils.isWeb(httpPair)) {
+ return super.createExposedSession(session, httpPair);
+ }
+ ServletRequest request = WebUtils.getRequest(httpPair);
+ ServletResponse response = WebUtils.getResponse(httpPair);
+ SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
+ return new DelegatingSession(this, sessionKey);
+ }
+
+ @Override
+ protected void createInternalSession(Session session, SessionContext context) {
+ super.createInternalSession(session, context);
+
+ if (WebUtils.isHttp(context)) {
+ HttpServletRequest request = WebUtils.getHttpRequest(context);
+ HttpServletResponse response = WebUtils.getHttpResponse(context);
+ log.trace("Request ID: {}, Created Session {}", request.getAttribute(AbstractShiroFilter.REQUEST_ID_ATTR_NAME), session.getId());
+
+ if (isSessionIdCookieEnabled()) {
+ Serializable sessionId = session.getId();
+ storeSessionId(sessionId, request, response);
+ } else {
+ log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId());
+ }
+
+ request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
+
+ addSessionReference(request, session);
+ }
+ }
+
+ @Override
+ protected Session getInternalSession(SessionKey key, Serializable sessionId) {
+ HttpServletRequest request = WebUtils.isHttp(key) ? WebUtils.getHttpRequest(key) : null;
+
+ //activeSessions is a request-specific concept. Only reference it if in the context of a request:
+ if (request == null) {
+ return super.getInternalSession(key, sessionId);
+ }
+
+ Session internal = activeSessions.get(sessionId);
+ if (internal == null) {
+ //not in first-level request cache, check the DAO:
+ internal = super.getInternalSession(key, sessionId);
+ if (internal != null) {
+ //found it. Update the reference count for post-request cleanup:
+ addSessionReference(request, internal);
+ }
+ } else {
+ //in first-level request cache. Update the reference count for post-request cleanup:
+ addSessionReference(request, internal.getId());
+ }
+
+
+ return internal;
+ }
+
+ private void addSessionReference(HttpServletRequest request, Session session) {
+ Serializable id = session.getId();
+ addSessionReference(request, id);
+ this.activeSessions.put(id, session);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addSessionReference(HttpServletRequest request, Serializable sessionId) {
+ Set<Serializable> ids = (Set<Serializable>) request.getAttribute(REFERENCED_SESSION_IDS);
+ if (ids == null) {
+ ids = new CopyOnWriteArraySet<Serializable>();
+ request.setAttribute(REFERENCED_SESSION_IDS, ids);
+ }
+ boolean added = ids.add(sessionId);
+ if (added) {
+ incrementReferencedSession(sessionId);
+ }
+ }
+
+ private int incrementReferencedSession(Serializable sessionId) {
+ AtomicInteger count = this.activeSessionRequestCounts.get(sessionId);
+ if (count == null) {
+ count = new AtomicInteger(0);
+ AtomicInteger previous = this.activeSessionRequestCounts.putIfAbsent(sessionId, count);
+ if (previous != null) {
+ count = previous;
+ }
+ }
+ return count.incrementAndGet();
+ }
+
+ @Override
+ protected void update(Session session, SessionKey key) {
+ if (!WebUtils.isHttp(key)) {
+ super.update(session, key);
+ }
+ //don't actually update - we'll defer that to the end of the request via
+ //onEvent(EndServletRequestEvent). Just keep a record that it is referenced for now:
+ HttpServletRequest request = WebUtils.getHttpRequest(key);
+ addSessionReference(request, session);
+ log.trace("Ignored mid-request DAO update. Update will occur at the end of the request.");
+ }
+
+ public void onEvent(Object event) {
+ if (event instanceof BeginServletRequestEvent) {
+ handle((BeginServletRequestEvent)event);
+ } else if (event instanceof EndServletRequestEvent) {
+ handle((EndServletRequestEvent)event);
+ }
+ }
+
+ private void handle(BeginServletRequestEvent event) {
+ //give the request a unique ID, useful for multi-threaded debugging:
+ assignRequestId(event);
+
+ //update any associated session's last access timestamp to ensure sessions timeout:
+ updateSessionLastAccessTimestampIfPossible(event);
+ }
+
+ private void assignRequestId(BeginServletRequestEvent event) {
+ RequestIdGenerator generator = getRequestIdGenerator();
+ if (generator != null) {
+ String id = generator.generateId(event);
+ if (id != null) {
+ event.getServletRequest().setAttribute(REQUEST_ID_ATTR_NAME, id);
+ }
+ }
+ }
+
+ private void updateSessionLastAccessTimestampIfPossible(BeginServletRequestEvent event) {
+ Session session = event.getSubject().getSession(false);
+ if (session != null) {
+ AccessTimestampEvaluator evaluator = getAccessTimestampEvaluator();
+ if (evaluator == null || evaluator.isUpdateAccessTimestamp(event)) {
+ try {
+ session.touch();
+ } catch (Throwable t) {
+ log.error("session.touch() method invocation has failed. Unable to update" +
+ "the corresponding session's last access time based on the incoming request.", t);
+ }
+ }
+ }
+ }
+
+ protected final void handle(EndServletRequestEvent event) {
+ ServletRequest request = event.getServletRequest();
+ if (request instanceof HttpServletRequest) {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ dereferenceSessions(httpRequest);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void dereferenceSessions(HttpServletRequest request) {
+ Set<Serializable> ids = (Set<Serializable>) request.getAttribute(REFERENCED_SESSION_IDS);
+ if (ids == null) {
+ return;
+ }
+
+ for (Serializable id : ids) {
+ int count = decrementReferencedSession(id);
+ if (count <= 0) {
+ String requestId = (String)request.getAttribute(AbstractShiroFilter.REQUEST_ID_ATTR_NAME);
+ Session session = activeSessions.remove(id);
+ if (session instanceof ValidatingSession) {
+ ValidatingSession vs = (ValidatingSession) session;
+ if (!vs.isValid() && isDeleteInvalidSessions()) {
+ getSessionDAO().delete(vs);
+ log.trace("Request ID: {}, Deleted DAO Session {}", requestId, id);
+ } else {
+ getSessionDAO().update(vs);
+ log.trace("Request ID: {}, Updated DAO Session {}", requestId, id);
+ }
+ } else {
+ getSessionDAO().update(session);
+ log.trace("Request ID: {}, Updated DAO Session {}", requestId, id);
+ }
+ }
+ }
+ }
+
+ private int decrementReferencedSession(Serializable sessionId) {
+ AtomicInteger count = this.activeSessionRequestCounts.get(sessionId);
+ if (count != null) {
+ int val = count.decrementAndGet();
+ if (val <= 0) {
+ this.activeSessionRequestCounts.remove(sessionId);
+ }
+ return val;
+ }
+ return 0;
+ }
+
+
+ @Override
+ public Serializable getSessionId(SessionKey key) {
+ Serializable id = super.getSessionId(key);
+ if (id == null && WebUtils.isWeb(key)) {
+ ServletRequest request = WebUtils.getRequest(key);
+ ServletResponse response = WebUtils.getResponse(key);
+ id = getSessionId(request, response);
+ }
+ return id;
+ }
+
+ protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
+ String id = getSessionIdCookieValue(request, response);
+ if (id != null) {
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+ ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
+ } else {
+ //not in a cookie, or cookie is disabled - try the request params as a fallback (i.e. URL rewriting):
+ id = request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+ if (id == null) {
+ //try lowercase:
+ id = request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME.toLowerCase());
+ }
+ if (id != null) {
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+ ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
+ }
+ }
+ 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.
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+ }
+ return id;
+ }
+
+ @Override
+ protected void onStop(Session session, SessionKey key, InvalidSessionException ise) {
+ super.onStop(session, key, ise);
+
+ if (WebUtils.isHttp(key)) {
+ HttpServletRequest request = WebUtils.getHttpRequest(key);
+ HttpServletResponse response = WebUtils.getHttpResponse(key);
+ if (ise != null) {
+ request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
+ }
+ log.debug("Session is stopped or invalid. Removing session ID cookie.");
+ getSessionIdCookie().removeFrom(request, response);
+ }
+ }
+
+ /**
+ * This is a native session manager implementation, so this method returns {@code false} always.
+ *
+ * @return {@code false} always
+ */
+ public boolean isServletContainerSessions() {
+ return false;
+ }
+}
Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/UuidRequestIdGenerator.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/UuidRequestIdGenerator.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/UuidRequestIdGenerator.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/UuidRequestIdGenerator.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,22 @@
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+/**
+ * Returns IDs based on the random {@link UUID}s.
+ *
+ * @see java.util.UUID#randomUUID()
+ * @since 1.3
+ */
+public class UuidRequestIdGenerator implements RequestIdGenerator {
+
+ private final Pattern PATTERN = Pattern.compile("-");
+
+ public String generateId(BeginServletRequestEvent event) {
+ String id = UUID.randomUUID().toString();
+ return PATTERN.matcher(id).replaceAll("").toUpperCase();
+ }
+}