org.redisson.spring.session.RedissonSessionRepository Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* 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.redisson.spring.session;
import org.redisson.api.*;
import org.redisson.api.listener.PatternMessageListener;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Deprecated. Use spring-session implementation based on Redisson Redis Data module
*
* @author Nikita Koksharov
*
*/
@Deprecated
public class RedissonSessionRepository implements FindByIndexNameSessionRepository,
PatternMessageListener {
static final String SESSION_ATTR_PREFIX = "session-attr:";
final class RedissonSession implements Session {
private String principalName;
private final MapSession delegate;
private RMap map;
RedissonSession() {
this.delegate = new MapSession();
map = redisson.getMap(keyPrefix + delegate.getId(), new CompositeCodec(StringCodec.INSTANCE, redisson.getConfig().getCodec()));
Map newMap = new HashMap(3);
newMap.put("session:creationTime", delegate.getCreationTime().toEpochMilli());
newMap.put("session:lastAccessedTime", delegate.getLastAccessedTime().toEpochMilli());
newMap.put("session:maxInactiveInterval", delegate.getMaxInactiveInterval().getSeconds());
map.putAll(newMap);
updateExpiration();
String channelName = getEventsChannelName(delegate.getId());
RTopic topic = redisson.getTopic(channelName, StringCodec.INSTANCE);
topic.publish(delegate.getId());
}
private void updateExpiration() {
if (delegate.getMaxInactiveInterval().getSeconds() > 0) {
redisson.getBucket(getExpiredKey(delegate.getId())).set("", delegate.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
map.expire(Duration.ofSeconds(delegate.getMaxInactiveInterval().getSeconds() + 60));
}
}
RedissonSession(MapSession session) {
this.delegate = session;
map = redisson.getMap(keyPrefix + session.getId(), new CompositeCodec(StringCodec.INSTANCE, redisson.getConfig().getCodec()));
principalName = resolvePrincipal(this);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public T getAttribute(String attributeName) {
return delegate.getAttribute(attributeName);
}
@Override
public Set getAttributeNames() {
return delegate.getAttributeNames();
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
if (attributeValue == null) {
removeAttribute(attributeName);
return;
}
delegate.setAttribute(attributeName, attributeValue);
if (map != null) {
map.fastPut(getSessionAttrNameKey(attributeName), attributeValue);
if (attributeName.equals(PRINCIPAL_NAME_INDEX_NAME)
|| attributeName.equals(SPRING_SECURITY_CONTEXT)) {
// remove old
if (principalName != null) {
RSet set = getPrincipalSet(principalName);
set.remove(getId());
}
principalName = resolvePrincipal(this);
if (principalName != null) {
RSet set = getPrincipalSet(principalName);
set.add(getId());
}
}
}
}
public void clearPrincipal() {
principalName = resolvePrincipal(this);
if (principalName != null) {
RSet set = getPrincipalSet(principalName);
set.remove(getId());
}
}
@Override
public void removeAttribute(String attributeName) {
delegate.removeAttribute(attributeName);
if (map != null) {
map.fastRemove(getSessionAttrNameKey(attributeName));
}
}
@Override
public Instant getCreationTime() {
return delegate.getCreationTime();
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
delegate.setLastAccessedTime(lastAccessedTime);
if (map != null) {
map.fastPut("session:lastAccessedTime", lastAccessedTime.toEpochMilli());
updateExpiration();
}
}
@Override
public Instant getLastAccessedTime() {
return delegate.getLastAccessedTime();
}
@Override
public void setMaxInactiveInterval(Duration interval) {
delegate.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", interval.getSeconds());
updateExpiration();
}
}
@Override
public Duration getMaxInactiveInterval() {
return delegate.getMaxInactiveInterval();
}
@Override
public boolean isExpired() {
return delegate.isExpired();
}
@Override
public String changeSessionId() {
String oldId = delegate.getId();
String id = delegate.changeSessionId();
RBatch batch = redisson.createBatch(BatchOptions.defaults());
batch.getBucket(getExpiredKey(oldId)).remainTimeToLiveAsync();
batch.getBucket(getExpiredKey(oldId)).deleteAsync();
batch.getMap(map.getName(), map.getCodec()).readAllMapAsync();
batch.getMap(map.getName()).deleteAsync();
BatchResult> res = batch.execute();
List> list = res.getResponses();
Long remainTTL = (Long) list.get(0);
Map oldState = (Map) list.get(2);
if (remainTTL == -2) {
// Either:
// - a parallel request also invoked changeSessionId() on this session, and the
// expiredKey for oldId had been deleted
// - sessions do not expire
remainTTL = delegate.getMaxInactiveInterval().toMillis();
}
RBatch batchNew = redisson.createBatch();
batchNew.getMap(keyPrefix + id, map.getCodec()).putAllAsync(oldState);
if (remainTTL > 0) {
batchNew.getBucket(getExpiredKey(id)).setAsync("", remainTTL, TimeUnit.MILLISECONDS);
}
batchNew.execute();
map = redisson.getMap(keyPrefix + id, map.getCodec());
return id;
}
}
private static final Logger log = LoggerFactory.getLogger(RedissonSessionRepository.class);
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
private RedissonClient redisson;
private ApplicationEventPublisher eventPublisher;
private RPatternTopic deletedTopic;
private RPatternTopic expiredTopic;
private RPatternTopic createdTopic;
private String keyPrefix = "spring:session:";
private Integer defaultMaxInactiveInterval;
public RedissonSessionRepository(RedissonClient redissonClient, ApplicationEventPublisher eventPublisher, String keyPrefix) {
this.redisson = redissonClient;
this.eventPublisher = eventPublisher;
if (StringUtils.hasText(keyPrefix)) {
this.keyPrefix = keyPrefix;
}
deletedTopic = this.redisson.getPatternTopic("__keyevent@*:del", StringCodec.INSTANCE);
expiredTopic = this.redisson.getPatternTopic("__keyevent@*:expired", StringCodec.INSTANCE);
createdTopic = this.redisson.getPatternTopic(getEventsChannelPrefix() + "*", StringCodec.INSTANCE);
// add listeners after all topics are created to avoid race and potential NPE if we get messages right away
deletedTopic.addListener(String.class, this);
expiredTopic.addListener(String.class, this);
createdTopic.addListener(String.class, this);
}
public RedissonSessionRepository(RedissonClient redissonClient, ApplicationEventPublisher eventPublisher) {
this(redissonClient, eventPublisher, null);
}
private MapSession loadSession(String sessionId) {
RMap map = redisson.getMap(keyPrefix + sessionId, new CompositeCodec(StringCodec.INSTANCE, redisson.getConfig().getCodec()));
Set> entrySet = map.readAllEntrySet();
if (entrySet.isEmpty()) {
return null;
}
MapSession delegate = new MapSession(sessionId);
for (Entry entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
delegate.setCreationTime(Instant.ofEpochMilli((Long) entry.getValue()));
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
delegate.setLastAccessedTime(Instant.ofEpochMilli((Long) entry.getValue()));
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
delegate.setMaxInactiveInterval(Duration.ofSeconds((Long) entry.getValue()));
} else if (entry.getKey().startsWith(SESSION_ATTR_PREFIX)) {
delegate.setAttribute(entry.getKey().substring(SESSION_ATTR_PREFIX.length()), entry.getValue());
}
}
return delegate;
}
@Override
public void onMessage(CharSequence pattern, CharSequence channel, String body) {
if (createdTopic.getPatternNames().contains(pattern.toString())) {
RedissonSession session = findById(body);
if (session != null) {
publishEvent(new SessionCreatedEvent(this, session));
}
} else if (deletedTopic.getPatternNames().contains(pattern.toString())) {
if (!body.startsWith(getExpiredKeyPrefix())) {
return;
}
String id = body.split(getExpiredKeyPrefix())[1];
MapSession mapSession = loadSession(id);
if (mapSession != null) {
RedissonSession session = new RedissonSession(mapSession);
session.clearPrincipal();
publishEvent(new SessionDeletedEvent(this, session));
}
} else if (expiredTopic.getPatternNames().contains(pattern.toString())) {
if (!body.startsWith(getExpiredKeyPrefix())) {
return;
}
String id = body.split(getExpiredKeyPrefix())[1];
MapSession mapSession = loadSession(id);
if (mapSession != null) {
RedissonSession session = new RedissonSession(mapSession);
session.clearPrincipal();
publishEvent(new SessionExpiredEvent(this, session));
}
}
}
private void publishEvent(ApplicationEvent event) {
try {
eventPublisher.publishEvent(event);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public RedissonSession createSession() {
RedissonSession session = new RedissonSession();
if (defaultMaxInactiveInterval != null) {
session.setMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval));
}
return session;
}
@Override
public void save(RedissonSession session) {
// session changes are stored in real-time
}
@Override
public RedissonSession findById(String id) {
MapSession mapSession = loadSession(id);
if (mapSession == null || mapSession.isExpired()) {
return null;
}
return new RedissonSession(mapSession);
}
@Override
public void deleteById(String id) {
RedissonSession session = findById(id);
if (session == null) {
return;
}
redisson.getBucket(getExpiredKey(id)).delete();
session.clearPrincipal();
session.setMaxInactiveInterval(Duration.ZERO);
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
String resolvePrincipal(Session session) {
String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
if (principalName != null) {
return principalName;
}
Object auth = session.getAttribute(SPRING_SECURITY_CONTEXT);
if (auth == null) {
return null;
}
Expression expression = SPEL_PARSER.parseExpression("authentication?.name");
return expression.getValue(auth, String.class);
}
String getEventsChannelName(String sessionId) {
return getEventsChannelPrefix() + sessionId;
}
String getExpiredKey(String sessionId) {
return getExpiredKeyPrefix() + sessionId;
}
String getExpiredKeyPrefix() {
return keyPrefix + "sessions:expires:";
}
String getEventsChannelPrefix() {
return keyPrefix + "created:event:";
}
String getPrincipalKey(String principalName) {
return keyPrefix + "index:" + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME + ":" + principalName;
}
String getSessionAttrNameKey(String name) {
return SESSION_ATTR_PREFIX + name;
}
@Override
public Map findByIndexNameAndIndexValue(String indexName, String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
RSet set = getPrincipalSet(indexValue);
Set sessionIds = set.readAll();
Map result = new HashMap();
for (String id : sessionIds) {
RedissonSession session = findById(id);
if (session != null) {
result.put(id, session);
}
}
return result;
}
private RSet getPrincipalSet(String indexValue) {
String principalKey = getPrincipalKey(indexValue);
return redisson.getSet(principalKey, StringCodec.INSTANCE);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy