Support querying for sessions by user identifier

Fixes gh-7
This commit is contained in:
Rob Winch
2015-08-14 16:27:56 -05:00
parent 77eb6cfd71
commit 881ca7c2d4
34 changed files with 8660 additions and 14 deletions

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* 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.springframework.session;
import java.util.Map;
/**
* Extends a basic {@link SessionRepository} to allow finding a session id by
* the principal name. The principal name is defined by the {@link Session}
* attribute with the name {@link Session#PRINCIPAL_NAME_ATTRIBUTE_NAME}.
*
* @author Rob Winch
*
* @param <S>
* the type of Session being managed by this
* {@link FindByPrincipalNameSessionRepository}
*/
public interface FindByPrincipalNameSessionRepository<S extends Session> extends SessionRepository<S> {
/**
* Find a Map of the session id to the {@link Session} of all sessions that
* contain the session attribute with the name
* {@link Session#PRINCIPAL_NAME_ATTRIBUTE_NAME} and the value of the
* specified principal name.
*
* @param principalName
* the principal name (i.e. username) to search for
* @return a Map (never null) of the session id to the {@link Session} of
* all sessions that contain the session attribute with the name
* {@link Session#PRINCIPAL_NAME_ATTRIBUTE_NAME} and the value of
* the specified principal name. If no results are found, an empty
* Map is returned.
*/
Map<String, S> findByPrincipalName(String principalName);
}

View File

@@ -26,6 +26,22 @@ import java.util.Set;
*/
public interface Session {
/**
* <p>
* A common session attribute that contains the current principal name (i.e.
* username).
* </p>
*
* <p>
* It is the responsibility of the developer to ensure the attribute
* is populated since Spring Session is not aware of the authentication
* mechanism being used.
* </p>
*
* @since 1.1
*/
String PRINCIPAL_NAME_ATTRIBUTE_NAME = Session.class.getName().concat(".PRINCIPAL_NAME_ATTRIBUTE_NAME");
/**
* Gets a unique string that identifies the {@link Session}
*

View File

@@ -35,6 +35,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByPrincipalNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@@ -246,23 +247,28 @@ import org.springframework.util.Assert;
*
* @author Rob Winch
*/
public class RedisOperationsSessionRepository implements SessionRepository<RedisOperationsSessionRepository.RedisSession>, MessageListener {
public class RedisOperationsSessionRepository implements FindByPrincipalNameSessionRepository<RedisOperationsSessionRepository.RedisSession>, MessageListener {
private static final Log logger = LogFactory.getLog(SessionMessageListener.class);
/**
* The prefix for each key in Redis used by Spring Session
*/
static final String SPRING_SESSION_KEY_PREFIX = "spring:session:";
/**
* The prefix for each key that contains a mapping of the Principal name (i.e. username) to the session ids.
*/
static final String PRINCIPAL_NAME_PREFIX = SPRING_SESSION_KEY_PREFIX + "index:" + Session.PRINCIPAL_NAME_ATTRIBUTE_NAME + ":";
/**
* The prefix for SessionCreated event channel. The suffix is the session id.
*/
private static final String SPRING_SESSION_CREATED_PREFIX = "spring:session:event:created:";
private static final Log logger = LogFactory.getLog(SessionMessageListener.class);
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
public void publishEvent(ApplicationEvent event) {
}
};
private static final String SPRING_SESSION_CREATED_PREFIX = SPRING_SESSION_KEY_PREFIX + "event:created:";
/**
* The prefix for each key of the Redis Hash representing a single session. The suffix is the unique session id.
*/
static final String BOUNDED_HASH_KEY_PREFIX = "spring:session:sessions:";
static final String BOUNDED_HASH_KEY_PREFIX = SPRING_SESSION_KEY_PREFIX + "sessions:";
/**
* The key in the Hash representing {@link org.springframework.session.ExpiringSession#getCreationTime()}
@@ -290,6 +296,11 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
private final RedisSessionExpirationPolicy expirationPolicy;
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
public void publishEvent(ApplicationEvent event) {
}
};
/**
* If non-null, this value is used to override the default value for {@link RedisSession#setMaxInactiveIntervalInSeconds(int)}.
*/
@@ -357,6 +368,24 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
return getSession(id, false);
}
public Map<String,RedisSession> findByPrincipalName(String principalName) {
String principalKey = getPrincipalKey(principalName);
Set<Object> sessionIds = sessionRedisOperations.boundSetOps(principalKey).members();
Map<String,RedisSession> sessions = new HashMap<String,RedisSession>(sessionIds.size());
for(Object id : sessionIds) {
RedisSession session = getSession((String) id);
if(session != null) {
session.setLastAccessedTime(session.originalLastAccessTime);
sessions.put(session.getId(), session);
}
}
return sessions;
}
private String getPrincipalKey(String principalName) {
return PRINCIPAL_NAME_PREFIX + principalName;
}
/**
*
* @param id the session id
@@ -376,7 +405,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
return null;
}
RedisSession result = new RedisSession(loaded);
result.originalLastAccessTime = loaded.getLastAccessedTime() + TimeUnit.SECONDS.toMillis(loaded.getMaxInactiveIntervalInSeconds());
result.originalLastAccessTime = loaded.getLastAccessedTime();
result.setLastAccessedTime(System.currentTimeMillis());
return result;
}
@@ -459,6 +488,11 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}
String principal = (String) session.getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME);
if(principal != null) {
sessionRedisOperations.boundSetOps(getPrincipalKey(principal)).remove(sessionId);
}
if(isDeleted) {
handleDeleted(sessionId, session);
} else {
@@ -640,9 +674,17 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
private void saveDelta() {
String sessionId = getId();
getSessionBoundHashOperations(sessionId).putAll(delta);
String key = getSessionAttrNameKey(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME);
if(delta.containsKey(key)) {
Object principal = delta.get(key);
String principalKey = getPrincipalKey((String) principal);
sessionRedisOperations.boundSetOps(principalKey).add(sessionId);
}
delta = new HashMap<String,Object>(delta.size());
expirationPolicy.onExpirationUpdated(originalLastAccessTime, this);
Long originalExpiration = originalLastAccessTime == null ? null : originalLastAccessTime + TimeUnit.SECONDS.toMillis(getMaxInactiveIntervalInSeconds()) ;
expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
}